svamp-cli 0.1.69 → 0.1.70
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.mjs +18 -18
- package/dist/{commands-CgWkG45c.mjs → commands-BodoXVL9.mjs} +1 -1
- package/dist/{commands-CNWZF0B9.mjs → commands-CIgrbviM.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-D2QVDgm_.mjs → package-B0d2rUXI.mjs} +1 -1
- package/dist/{run-DxmtlxnM.mjs → run-C7CEDmD4.mjs} +422 -353
- package/dist/{run-DqPEyYsf.mjs → run-CgSj6KtU.mjs} +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-
|
|
1
|
+
import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-C7CEDmD4.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -109,7 +109,7 @@ async function main() {
|
|
|
109
109
|
const { handleServiceCommand } = await import('./commands-CF32XIau.mjs');
|
|
110
110
|
await handleServiceCommand();
|
|
111
111
|
} else if (subcommand === "process" || subcommand === "proc") {
|
|
112
|
-
const { processCommand } = await import('./commands-
|
|
112
|
+
const { processCommand } = await import('./commands-CIgrbviM.mjs');
|
|
113
113
|
let machineId;
|
|
114
114
|
const processArgs = args.slice(1);
|
|
115
115
|
const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
|
|
@@ -127,7 +127,7 @@ async function main() {
|
|
|
127
127
|
} else if (!subcommand || subcommand === "start") {
|
|
128
128
|
await handleInteractiveCommand();
|
|
129
129
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
130
|
-
const pkg = await import('./package-
|
|
130
|
+
const pkg = await import('./package-B0d2rUXI.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
131
131
|
console.log(`svamp version: ${pkg.default.version}`);
|
|
132
132
|
} else {
|
|
133
133
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -136,7 +136,7 @@ async function main() {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
async function handleInteractiveCommand() {
|
|
139
|
-
const { runInteractive } = await import('./run-
|
|
139
|
+
const { runInteractive } = await import('./run-CgSj6KtU.mjs');
|
|
140
140
|
const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
|
|
141
141
|
let directory = process.cwd();
|
|
142
142
|
let resumeSessionId;
|
|
@@ -181,7 +181,7 @@ async function handleAgentCommand() {
|
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
183
|
if (agentArgs[0] === "list") {
|
|
184
|
-
const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-
|
|
184
|
+
const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.i; });
|
|
185
185
|
console.log("Known agents:");
|
|
186
186
|
for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
|
|
187
187
|
console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
|
|
@@ -193,7 +193,7 @@ async function handleAgentCommand() {
|
|
|
193
193
|
console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
|
-
const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-
|
|
196
|
+
const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.i; });
|
|
197
197
|
let cwd = process.cwd();
|
|
198
198
|
const filteredArgs = [];
|
|
199
199
|
for (let i = 0; i < agentArgs.length; i++) {
|
|
@@ -217,12 +217,12 @@ async function handleAgentCommand() {
|
|
|
217
217
|
console.log(`Starting ${config.agentName} agent in ${cwd}...`);
|
|
218
218
|
let backend;
|
|
219
219
|
if (KNOWN_MCP_AGENTS[config.agentName]) {
|
|
220
|
-
const { CodexMcpBackend } = await import('./run-
|
|
220
|
+
const { CodexMcpBackend } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.j; });
|
|
221
221
|
backend = new CodexMcpBackend({ cwd, log: logFn });
|
|
222
222
|
} else {
|
|
223
|
-
const { AcpBackend } = await import('./run-
|
|
224
|
-
const { GeminiTransport } = await import('./run-
|
|
225
|
-
const { DefaultTransport } = await import('./run-
|
|
223
|
+
const { AcpBackend } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.h; });
|
|
224
|
+
const { GeminiTransport } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.G; });
|
|
225
|
+
const { DefaultTransport } = await import('./run-C7CEDmD4.mjs').then(function (n) { return n.D; });
|
|
226
226
|
const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
|
|
227
227
|
backend = new AcpBackend({
|
|
228
228
|
agentName: config.agentName,
|
|
@@ -340,7 +340,7 @@ async function handleSessionCommand() {
|
|
|
340
340
|
printSessionHelp();
|
|
341
341
|
return;
|
|
342
342
|
}
|
|
343
|
-
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-
|
|
343
|
+
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-BodoXVL9.mjs');
|
|
344
344
|
const parseFlagStr = (flag, shortFlag) => {
|
|
345
345
|
for (let i = 1; i < sessionArgs.length; i++) {
|
|
346
346
|
if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
|
|
@@ -400,7 +400,7 @@ async function handleSessionCommand() {
|
|
|
400
400
|
allowDomain.push(sessionArgs[++i]);
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
-
const { parseShareArg } = await import('./commands-
|
|
403
|
+
const { parseShareArg } = await import('./commands-BodoXVL9.mjs');
|
|
404
404
|
const shareEntries = share.map((s) => parseShareArg(s));
|
|
405
405
|
await sessionSpawn(agent, dir, targetMachineId, {
|
|
406
406
|
message,
|
|
@@ -484,7 +484,7 @@ async function handleSessionCommand() {
|
|
|
484
484
|
console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
|
|
485
485
|
process.exit(1);
|
|
486
486
|
}
|
|
487
|
-
const { sessionApprove } = await import('./commands-
|
|
487
|
+
const { sessionApprove } = await import('./commands-BodoXVL9.mjs');
|
|
488
488
|
const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
|
|
489
489
|
await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
|
|
490
490
|
json: hasFlag("--json")
|
|
@@ -494,7 +494,7 @@ async function handleSessionCommand() {
|
|
|
494
494
|
console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
|
|
495
495
|
process.exit(1);
|
|
496
496
|
}
|
|
497
|
-
const { sessionDeny } = await import('./commands-
|
|
497
|
+
const { sessionDeny } = await import('./commands-BodoXVL9.mjs');
|
|
498
498
|
const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
|
|
499
499
|
await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
|
|
500
500
|
json: hasFlag("--json")
|
|
@@ -597,7 +597,7 @@ async function handleMachineCommand() {
|
|
|
597
597
|
return;
|
|
598
598
|
}
|
|
599
599
|
if (machineSubcommand === "share") {
|
|
600
|
-
const { machineShare } = await import('./commands-
|
|
600
|
+
const { machineShare } = await import('./commands-BodoXVL9.mjs');
|
|
601
601
|
let machineId;
|
|
602
602
|
const shareArgs = [];
|
|
603
603
|
for (let i = 1; i < machineArgs.length; i++) {
|
|
@@ -627,7 +627,7 @@ async function handleMachineCommand() {
|
|
|
627
627
|
}
|
|
628
628
|
await machineShare(machineId, { add, remove, list, configPath, showConfig });
|
|
629
629
|
} else if (machineSubcommand === "exec") {
|
|
630
|
-
const { machineExec } = await import('./commands-
|
|
630
|
+
const { machineExec } = await import('./commands-BodoXVL9.mjs');
|
|
631
631
|
let machineId;
|
|
632
632
|
let cwd;
|
|
633
633
|
const cmdParts = [];
|
|
@@ -647,7 +647,7 @@ async function handleMachineCommand() {
|
|
|
647
647
|
}
|
|
648
648
|
await machineExec(machineId, command, cwd);
|
|
649
649
|
} else if (machineSubcommand === "info") {
|
|
650
|
-
const { machineInfo } = await import('./commands-
|
|
650
|
+
const { machineInfo } = await import('./commands-BodoXVL9.mjs');
|
|
651
651
|
let machineId;
|
|
652
652
|
for (let i = 1; i < machineArgs.length; i++) {
|
|
653
653
|
if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
|
|
@@ -670,7 +670,7 @@ async function handleMachineCommand() {
|
|
|
670
670
|
const { machineNotify } = await import('./agentCommands-C6iGblcL.mjs');
|
|
671
671
|
await machineNotify(message, level);
|
|
672
672
|
} else if (machineSubcommand === "ls") {
|
|
673
|
-
const { machineLs } = await import('./commands-
|
|
673
|
+
const { machineLs } = await import('./commands-BodoXVL9.mjs');
|
|
674
674
|
let machineId;
|
|
675
675
|
let showHidden = false;
|
|
676
676
|
let path;
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-
|
|
5
|
+
import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-C7CEDmD4.mjs';
|
|
6
6
|
import 'os';
|
|
7
7
|
import 'fs/promises';
|
|
8
8
|
import 'fs';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { connectAndGetMachine } from './commands-
|
|
3
|
+
import { connectAndGetMachine } from './commands-BodoXVL9.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:child_process';
|
|
6
6
|
import 'node:path';
|
|
7
7
|
import 'node:os';
|
|
8
|
-
import './run-
|
|
8
|
+
import './run-C7CEDmD4.mjs';
|
|
9
9
|
import 'os';
|
|
10
10
|
import 'fs/promises';
|
|
11
11
|
import 'url';
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-
|
|
1
|
+
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-C7CEDmD4.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os__default from 'os';
|
|
2
|
-
import fs, { mkdir as mkdir$1, readdir, readFile, writeFile, rename, unlink } from 'fs/promises';
|
|
2
|
+
import fs, { mkdir as mkdir$1, readdir, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
|
|
3
3
|
import { readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync, existsSync as existsSync$1, copyFileSync, unlinkSync, watch, rmdirSync } from 'fs';
|
|
4
4
|
import path, { join, dirname, resolve, basename } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
@@ -15,7 +15,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
15
15
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
16
16
|
import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
17
17
|
import { z } from 'zod';
|
|
18
|
-
import { mkdir, rm, chmod, access, mkdtemp, copyFile } from 'node:fs/promises';
|
|
18
|
+
import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile } from 'node:fs/promises';
|
|
19
19
|
import { promisify } from 'node:util';
|
|
20
20
|
|
|
21
21
|
let connectToServerFn = null;
|
|
@@ -907,353 +907,351 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
907
907
|
});
|
|
908
908
|
return msg;
|
|
909
909
|
};
|
|
910
|
-
const
|
|
911
|
-
{
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return { id: existing.id, seq: existing.seq, localId: existing.localId };
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
let parsed = content;
|
|
937
|
-
if (typeof parsed === "string") {
|
|
938
|
-
try {
|
|
939
|
-
parsed = JSON.parse(parsed);
|
|
940
|
-
} catch {
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
if (parsed && typeof parsed.content === "string" && !parsed.role) {
|
|
944
|
-
try {
|
|
945
|
-
const inner = JSON.parse(parsed.content);
|
|
946
|
-
if (inner && typeof inner === "object") parsed = inner;
|
|
947
|
-
} catch {
|
|
948
|
-
}
|
|
910
|
+
const serviceDefinition = {
|
|
911
|
+
id: `svamp-session-${sessionId}`,
|
|
912
|
+
name: `Svamp Session ${sessionId.slice(0, 8)}`,
|
|
913
|
+
type: "svamp-session",
|
|
914
|
+
config: { visibility: "unlisted", require_context: true },
|
|
915
|
+
// ── Messages ──
|
|
916
|
+
getMessages: async (afterSeq, limit, context) => {
|
|
917
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
918
|
+
const after = afterSeq ?? 0;
|
|
919
|
+
const lim = Math.min(limit ?? 100, 500);
|
|
920
|
+
const filtered = messages.filter((m) => m.seq > after);
|
|
921
|
+
const page = filtered.slice(0, lim);
|
|
922
|
+
return {
|
|
923
|
+
messages: page,
|
|
924
|
+
hasMore: filtered.length > lim
|
|
925
|
+
};
|
|
926
|
+
},
|
|
927
|
+
sendMessage: async (content, localId, meta, context) => {
|
|
928
|
+
authorizeRequest(context, metadata.sharing, "interact");
|
|
929
|
+
if (localId) {
|
|
930
|
+
const existing = messages.find((m) => m.localId === localId);
|
|
931
|
+
if (existing) {
|
|
932
|
+
return { id: existing.id, seq: existing.seq, localId: existing.localId };
|
|
949
933
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
createdAt: Date.now(),
|
|
957
|
-
updatedAt: Date.now()
|
|
958
|
-
};
|
|
959
|
-
messages.push(msg);
|
|
960
|
-
if (messages.length > 1e3) messages.splice(0, messages.length - 1e3);
|
|
961
|
-
if (options?.messagesDir) {
|
|
962
|
-
appendMessage(options.messagesDir, sessionId, msg);
|
|
934
|
+
}
|
|
935
|
+
let parsed = content;
|
|
936
|
+
if (typeof parsed === "string") {
|
|
937
|
+
try {
|
|
938
|
+
parsed = JSON.parse(parsed);
|
|
939
|
+
} catch {
|
|
963
940
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
return { id: msg.id, seq: msg.seq, localId: msg.localId };
|
|
971
|
-
},
|
|
972
|
-
// ── Metadata ──
|
|
973
|
-
getMetadata: async (context) => {
|
|
974
|
-
authorizeRequest(context, metadata.sharing, "view");
|
|
975
|
-
return {
|
|
976
|
-
metadata,
|
|
977
|
-
version: metadataVersion
|
|
978
|
-
};
|
|
979
|
-
},
|
|
980
|
-
updateMetadata: async (newMetadata, expectedVersion, context) => {
|
|
981
|
-
authorizeRequest(context, metadata.sharing, "admin");
|
|
982
|
-
if (expectedVersion !== void 0 && expectedVersion !== metadataVersion) {
|
|
983
|
-
return {
|
|
984
|
-
result: "version-mismatch",
|
|
985
|
-
version: metadataVersion,
|
|
986
|
-
metadata
|
|
987
|
-
};
|
|
941
|
+
}
|
|
942
|
+
if (parsed && typeof parsed.content === "string" && !parsed.role) {
|
|
943
|
+
try {
|
|
944
|
+
const inner = JSON.parse(parsed.content);
|
|
945
|
+
if (inner && typeof inner === "object") parsed = inner;
|
|
946
|
+
} catch {
|
|
988
947
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
948
|
+
}
|
|
949
|
+
const wrappedContent = parsed && parsed.role === "user" ? { role: "user", content: parsed.content } : { role: "user", content: { type: "text", text: typeof parsed === "string" ? parsed : JSON.stringify(parsed) } };
|
|
950
|
+
const msg = {
|
|
951
|
+
id: randomUUID(),
|
|
952
|
+
seq: nextSeq++,
|
|
953
|
+
content: wrappedContent,
|
|
954
|
+
localId: localId || randomUUID(),
|
|
955
|
+
createdAt: Date.now(),
|
|
956
|
+
updatedAt: Date.now()
|
|
957
|
+
};
|
|
958
|
+
messages.push(msg);
|
|
959
|
+
if (messages.length > 1e3) messages.splice(0, messages.length - 1e3);
|
|
960
|
+
if (options?.messagesDir) {
|
|
961
|
+
appendMessage(options.messagesDir, sessionId, msg);
|
|
962
|
+
}
|
|
963
|
+
notifyListeners({
|
|
964
|
+
type: "new-message",
|
|
965
|
+
sessionId,
|
|
966
|
+
message: msg
|
|
967
|
+
});
|
|
968
|
+
callbacks.onUserMessage(content, meta);
|
|
969
|
+
return { id: msg.id, seq: msg.seq, localId: msg.localId };
|
|
970
|
+
},
|
|
971
|
+
// ── Metadata ──
|
|
972
|
+
getMetadata: async (context) => {
|
|
973
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
974
|
+
return {
|
|
975
|
+
metadata,
|
|
976
|
+
version: metadataVersion
|
|
977
|
+
};
|
|
978
|
+
},
|
|
979
|
+
updateMetadata: async (newMetadata, expectedVersion, context) => {
|
|
980
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
981
|
+
if (expectedVersion !== void 0 && expectedVersion !== metadataVersion) {
|
|
997
982
|
return {
|
|
998
|
-
result: "
|
|
983
|
+
result: "version-mismatch",
|
|
999
984
|
version: metadataVersion,
|
|
1000
985
|
metadata
|
|
1001
986
|
};
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
987
|
+
}
|
|
988
|
+
metadata = newMetadata;
|
|
989
|
+
metadataVersion++;
|
|
990
|
+
notifyListeners({
|
|
991
|
+
type: "update-session",
|
|
992
|
+
sessionId,
|
|
993
|
+
metadata: { value: metadata, version: metadataVersion }
|
|
994
|
+
});
|
|
995
|
+
callbacks.onMetadataUpdate?.(metadata);
|
|
996
|
+
return {
|
|
997
|
+
result: "success",
|
|
998
|
+
version: metadataVersion,
|
|
999
|
+
metadata
|
|
1000
|
+
};
|
|
1001
|
+
},
|
|
1002
|
+
/**
|
|
1003
|
+
* Patch the session config file (.svamp/{sessionId}/config.json).
|
|
1004
|
+
* Used by the frontend to set title, session_link, ralph_loop, etc.
|
|
1005
|
+
* Null values remove keys from the config.
|
|
1006
|
+
*/
|
|
1007
|
+
updateConfig: async (patch, context) => {
|
|
1008
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1009
|
+
callbacks.onUpdateConfig?.(patch);
|
|
1010
|
+
return { success: true };
|
|
1011
|
+
},
|
|
1012
|
+
// ── Agent State ──
|
|
1013
|
+
getAgentState: async (context) => {
|
|
1014
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
1015
|
+
return {
|
|
1016
|
+
agentState,
|
|
1017
|
+
version: agentStateVersion
|
|
1018
|
+
};
|
|
1019
|
+
},
|
|
1020
|
+
updateAgentState: async (newState, expectedVersion, context) => {
|
|
1021
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1022
|
+
if (expectedVersion !== void 0 && expectedVersion !== agentStateVersion) {
|
|
1037
1023
|
return {
|
|
1038
|
-
result: "
|
|
1024
|
+
result: "version-mismatch",
|
|
1039
1025
|
version: agentStateVersion,
|
|
1040
1026
|
agentState
|
|
1041
1027
|
};
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1028
|
+
}
|
|
1029
|
+
agentState = newState;
|
|
1030
|
+
agentStateVersion++;
|
|
1031
|
+
notifyListeners({
|
|
1032
|
+
type: "update-session",
|
|
1033
|
+
sessionId,
|
|
1034
|
+
agentState: { value: agentState, version: agentStateVersion }
|
|
1035
|
+
});
|
|
1036
|
+
return {
|
|
1037
|
+
result: "success",
|
|
1038
|
+
version: agentStateVersion,
|
|
1039
|
+
agentState
|
|
1040
|
+
};
|
|
1041
|
+
},
|
|
1042
|
+
// ── Session Control RPCs ──
|
|
1043
|
+
abort: async (context) => {
|
|
1044
|
+
authorizeRequest(context, metadata.sharing, "interact");
|
|
1045
|
+
callbacks.onAbort();
|
|
1046
|
+
return { success: true };
|
|
1047
|
+
},
|
|
1048
|
+
permissionResponse: async (params, context) => {
|
|
1049
|
+
authorizeRequest(context, metadata.sharing, "interact");
|
|
1050
|
+
callbacks.onPermissionResponse(params);
|
|
1051
|
+
return { success: true };
|
|
1052
|
+
},
|
|
1053
|
+
switchMode: async (mode, context) => {
|
|
1054
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1055
|
+
callbacks.onSwitchMode(mode);
|
|
1056
|
+
return { success: true };
|
|
1057
|
+
},
|
|
1058
|
+
restartClaude: async (context) => {
|
|
1059
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1060
|
+
return await callbacks.onRestartClaude();
|
|
1061
|
+
},
|
|
1062
|
+
killSession: async (context) => {
|
|
1063
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1064
|
+
callbacks.onKillSession();
|
|
1065
|
+
return { success: true };
|
|
1066
|
+
},
|
|
1067
|
+
// ── Activity ──
|
|
1068
|
+
keepAlive: async (thinking, mode, context) => {
|
|
1069
|
+
authorizeRequest(context, metadata.sharing, "interact");
|
|
1070
|
+
lastActivity = { active: true, thinking: thinking || false, mode: mode || "remote", time: Date.now() };
|
|
1071
|
+
notifyListeners({
|
|
1072
|
+
type: "activity",
|
|
1073
|
+
sessionId,
|
|
1074
|
+
...lastActivity
|
|
1075
|
+
});
|
|
1076
|
+
},
|
|
1077
|
+
sessionEnd: async (context) => {
|
|
1078
|
+
authorizeRequest(context, metadata.sharing, "interact");
|
|
1079
|
+
lastActivity = { active: false, thinking: false, mode: "remote", time: Date.now() };
|
|
1080
|
+
notifyListeners({
|
|
1081
|
+
type: "activity",
|
|
1082
|
+
sessionId,
|
|
1083
|
+
...lastActivity
|
|
1084
|
+
});
|
|
1085
|
+
},
|
|
1086
|
+
// ── Activity State Query ──
|
|
1087
|
+
getActivityState: async (context) => {
|
|
1088
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
1089
|
+
const pendingPermissions = agentState?.requests ? Object.entries(agentState.requests).filter(([, req]) => req.status === "pending" || !req.status).map(([id, req]) => ({
|
|
1090
|
+
id,
|
|
1091
|
+
tool: req.tool,
|
|
1092
|
+
arguments: req.arguments,
|
|
1093
|
+
createdAt: req.createdAt
|
|
1094
|
+
})) : [];
|
|
1095
|
+
return { ...lastActivity, sessionId, pendingPermissions };
|
|
1096
|
+
},
|
|
1097
|
+
// ── File Operations (optional, admin-only) ──
|
|
1098
|
+
readFile: async (path, context) => {
|
|
1099
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1100
|
+
if (!callbacks.onReadFile) throw new Error("readFile not supported");
|
|
1101
|
+
return await callbacks.onReadFile(path);
|
|
1102
|
+
},
|
|
1103
|
+
writeFile: async (path, content, context) => {
|
|
1104
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1105
|
+
if (!callbacks.onWriteFile) throw new Error("writeFile not supported");
|
|
1106
|
+
await callbacks.onWriteFile(path, content);
|
|
1107
|
+
return { success: true };
|
|
1108
|
+
},
|
|
1109
|
+
listDirectory: async (path, context) => {
|
|
1110
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1111
|
+
if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
|
|
1112
|
+
return await callbacks.onListDirectory(path);
|
|
1113
|
+
},
|
|
1114
|
+
bash: async (command, cwd, context) => {
|
|
1115
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1116
|
+
if (!callbacks.onBash) throw new Error("bash not supported");
|
|
1117
|
+
return await callbacks.onBash(command, cwd);
|
|
1118
|
+
},
|
|
1119
|
+
ripgrep: async (args, cwd, context) => {
|
|
1120
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1121
|
+
if (!callbacks.onRipgrep) throw new Error("ripgrep not supported");
|
|
1122
|
+
try {
|
|
1123
|
+
const stdout = await callbacks.onRipgrep(args, cwd);
|
|
1124
|
+
return { success: true, stdout, stderr: "", exitCode: 0 };
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
return { success: false, stdout: "", stderr: err.message || "", exitCode: 1, error: err.message };
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
getDirectoryTree: async (path, maxDepth, context) => {
|
|
1130
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1131
|
+
if (!callbacks.onGetDirectoryTree) throw new Error("getDirectoryTree not supported");
|
|
1132
|
+
return await callbacks.onGetDirectoryTree(path, maxDepth ?? 3);
|
|
1133
|
+
},
|
|
1134
|
+
// ── Sharing Management ──
|
|
1135
|
+
getSharing: async (context) => {
|
|
1136
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
1137
|
+
return { sharing: metadata.sharing || null };
|
|
1138
|
+
},
|
|
1139
|
+
/** Returns the caller's effective role (null if no access). Does not throw. */
|
|
1140
|
+
getEffectiveRole: async (context) => {
|
|
1141
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
1142
|
+
const role = getEffectiveRole(context, metadata.sharing);
|
|
1143
|
+
return { role };
|
|
1144
|
+
},
|
|
1145
|
+
updateSharing: async (newSharing, context) => {
|
|
1146
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1147
|
+
if (metadata.sharing && context?.user?.email && metadata.sharing.owner && context.user.email.toLowerCase() !== metadata.sharing.owner.toLowerCase()) {
|
|
1148
|
+
throw new Error("Only the session owner can update sharing settings");
|
|
1149
|
+
}
|
|
1150
|
+
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
1151
|
+
newSharing = { ...newSharing, owner: context.user.email };
|
|
1152
|
+
}
|
|
1153
|
+
metadata = { ...metadata, sharing: newSharing };
|
|
1154
|
+
metadataVersion++;
|
|
1155
|
+
notifyListeners({
|
|
1156
|
+
type: "update-session",
|
|
1157
|
+
sessionId,
|
|
1158
|
+
metadata: { value: metadata, version: metadataVersion }
|
|
1159
|
+
});
|
|
1160
|
+
callbacks.onSharingUpdate?.(newSharing);
|
|
1161
|
+
return { success: true, sharing: newSharing };
|
|
1162
|
+
},
|
|
1163
|
+
/** Update security context and restart the agent process with new rules */
|
|
1164
|
+
updateSecurityContext: async (newSecurityContext, context) => {
|
|
1165
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1166
|
+
if (metadata.sharing && context?.user?.email && metadata.sharing.owner && context.user.email.toLowerCase() !== metadata.sharing.owner.toLowerCase()) {
|
|
1167
|
+
throw new Error("Only the session owner can update security context");
|
|
1168
|
+
}
|
|
1169
|
+
if (!callbacks.onUpdateSecurityContext) {
|
|
1170
|
+
throw new Error("Security context updates are not supported for this session");
|
|
1171
|
+
}
|
|
1172
|
+
metadata = { ...metadata, securityContext: newSecurityContext };
|
|
1173
|
+
metadataVersion++;
|
|
1174
|
+
notifyListeners({
|
|
1175
|
+
type: "update-session",
|
|
1176
|
+
sessionId,
|
|
1177
|
+
metadata: { value: metadata, version: metadataVersion }
|
|
1178
|
+
});
|
|
1179
|
+
return await callbacks.onUpdateSecurityContext(newSecurityContext);
|
|
1180
|
+
},
|
|
1181
|
+
/** Apply a new system prompt and restart the agent process */
|
|
1182
|
+
applySystemPrompt: async (prompt, context) => {
|
|
1183
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
1184
|
+
if (!callbacks.onApplySystemPrompt) {
|
|
1185
|
+
throw new Error("System prompt updates are not supported for this session");
|
|
1186
|
+
}
|
|
1187
|
+
return await callbacks.onApplySystemPrompt(prompt);
|
|
1188
|
+
},
|
|
1189
|
+
// ── Listener Registration ──
|
|
1190
|
+
registerListener: async (callback, context) => {
|
|
1191
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
1192
|
+
listeners.push(callback);
|
|
1193
|
+
const replayMessages = messages.slice(-50);
|
|
1194
|
+
const REPLAY_MESSAGE_TIMEOUT_MS = 1e4;
|
|
1195
|
+
for (const msg of replayMessages) {
|
|
1196
|
+
if (listeners.indexOf(callback) < 0) break;
|
|
1123
1197
|
try {
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1198
|
+
const result = callback.onUpdate({
|
|
1199
|
+
type: "new-message",
|
|
1200
|
+
sessionId,
|
|
1201
|
+
message: msg
|
|
1202
|
+
});
|
|
1203
|
+
if (result && typeof result.catch === "function") {
|
|
1204
|
+
try {
|
|
1205
|
+
await Promise.race([
|
|
1206
|
+
result,
|
|
1207
|
+
new Promise(
|
|
1208
|
+
(_, reject) => setTimeout(() => reject(new Error("Replay message timeout")), REPLAY_MESSAGE_TIMEOUT_MS)
|
|
1209
|
+
)
|
|
1210
|
+
]);
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
console.error(`[HYPHA SESSION ${sessionId}] Replay listener error, removing:`, err?.message ?? err);
|
|
1213
|
+
removeListener(callback, "replay error");
|
|
1214
|
+
return { success: false, error: "Listener removed during replay" };
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1126
1217
|
} catch (err) {
|
|
1127
|
-
|
|
1218
|
+
console.error(`[HYPHA SESSION ${sessionId}] Replay listener error, removing:`, err?.message ?? err);
|
|
1219
|
+
removeListener(callback, "replay error");
|
|
1220
|
+
return { success: false, error: "Listener removed during replay" };
|
|
1128
1221
|
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
// ── Sharing Management ──
|
|
1136
|
-
getSharing: async (context) => {
|
|
1137
|
-
authorizeRequest(context, metadata.sharing, "view");
|
|
1138
|
-
return { sharing: metadata.sharing || null };
|
|
1139
|
-
},
|
|
1140
|
-
/** Returns the caller's effective role (null if no access). Does not throw. */
|
|
1141
|
-
getEffectiveRole: async (context) => {
|
|
1142
|
-
authorizeRequest(context, metadata.sharing, "view");
|
|
1143
|
-
const role = getEffectiveRole(context, metadata.sharing);
|
|
1144
|
-
return { role };
|
|
1145
|
-
},
|
|
1146
|
-
updateSharing: async (newSharing, context) => {
|
|
1147
|
-
authorizeRequest(context, metadata.sharing, "admin");
|
|
1148
|
-
if (metadata.sharing && context?.user?.email && metadata.sharing.owner && context.user.email.toLowerCase() !== metadata.sharing.owner.toLowerCase()) {
|
|
1149
|
-
throw new Error("Only the session owner can update sharing settings");
|
|
1150
|
-
}
|
|
1151
|
-
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
1152
|
-
newSharing = { ...newSharing, owner: context.user.email };
|
|
1153
|
-
}
|
|
1154
|
-
metadata = { ...metadata, sharing: newSharing };
|
|
1155
|
-
metadataVersion++;
|
|
1156
|
-
notifyListeners({
|
|
1222
|
+
}
|
|
1223
|
+
if (listeners.indexOf(callback) < 0) {
|
|
1224
|
+
return { success: false, error: "Listener was removed during replay" };
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
const result = callback.onUpdate({
|
|
1157
1228
|
type: "update-session",
|
|
1158
1229
|
sessionId,
|
|
1159
|
-
metadata: { value: metadata, version: metadataVersion }
|
|
1230
|
+
metadata: { value: metadata, version: metadataVersion },
|
|
1231
|
+
agentState: { value: agentState, version: agentStateVersion }
|
|
1160
1232
|
});
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
/** Update security context and restart the agent process with new rules */
|
|
1165
|
-
updateSecurityContext: async (newSecurityContext, context) => {
|
|
1166
|
-
authorizeRequest(context, metadata.sharing, "admin");
|
|
1167
|
-
if (metadata.sharing && context?.user?.email && metadata.sharing.owner && context.user.email.toLowerCase() !== metadata.sharing.owner.toLowerCase()) {
|
|
1168
|
-
throw new Error("Only the session owner can update security context");
|
|
1169
|
-
}
|
|
1170
|
-
if (!callbacks.onUpdateSecurityContext) {
|
|
1171
|
-
throw new Error("Security context updates are not supported for this session");
|
|
1233
|
+
if (result && typeof result.catch === "function") {
|
|
1234
|
+
result.catch(() => {
|
|
1235
|
+
});
|
|
1172
1236
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1237
|
+
} catch {
|
|
1238
|
+
}
|
|
1239
|
+
try {
|
|
1240
|
+
const result = callback.onUpdate({
|
|
1241
|
+
type: "activity",
|
|
1177
1242
|
sessionId,
|
|
1178
|
-
|
|
1243
|
+
...lastActivity
|
|
1179
1244
|
});
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
/** Apply a new system prompt and restart the agent process */
|
|
1183
|
-
applySystemPrompt: async (prompt, context) => {
|
|
1184
|
-
authorizeRequest(context, metadata.sharing, "admin");
|
|
1185
|
-
if (!callbacks.onApplySystemPrompt) {
|
|
1186
|
-
throw new Error("System prompt updates are not supported for this session");
|
|
1187
|
-
}
|
|
1188
|
-
return await callbacks.onApplySystemPrompt(prompt);
|
|
1189
|
-
},
|
|
1190
|
-
// ── Listener Registration ──
|
|
1191
|
-
registerListener: async (callback, context) => {
|
|
1192
|
-
authorizeRequest(context, metadata.sharing, "view");
|
|
1193
|
-
listeners.push(callback);
|
|
1194
|
-
const replayMessages = messages.slice(-50);
|
|
1195
|
-
const REPLAY_MESSAGE_TIMEOUT_MS = 1e4;
|
|
1196
|
-
for (const msg of replayMessages) {
|
|
1197
|
-
if (listeners.indexOf(callback) < 0) break;
|
|
1198
|
-
try {
|
|
1199
|
-
const result = callback.onUpdate({
|
|
1200
|
-
type: "new-message",
|
|
1201
|
-
sessionId,
|
|
1202
|
-
message: msg
|
|
1203
|
-
});
|
|
1204
|
-
if (result && typeof result.catch === "function") {
|
|
1205
|
-
try {
|
|
1206
|
-
await Promise.race([
|
|
1207
|
-
result,
|
|
1208
|
-
new Promise(
|
|
1209
|
-
(_, reject) => setTimeout(() => reject(new Error("Replay message timeout")), REPLAY_MESSAGE_TIMEOUT_MS)
|
|
1210
|
-
)
|
|
1211
|
-
]);
|
|
1212
|
-
} catch (err) {
|
|
1213
|
-
console.error(`[HYPHA SESSION ${sessionId}] Replay listener error, removing:`, err?.message ?? err);
|
|
1214
|
-
removeListener(callback, "replay error");
|
|
1215
|
-
return { success: false, error: "Listener removed during replay" };
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
} catch (err) {
|
|
1219
|
-
console.error(`[HYPHA SESSION ${sessionId}] Replay listener error, removing:`, err?.message ?? err);
|
|
1220
|
-
removeListener(callback, "replay error");
|
|
1221
|
-
return { success: false, error: "Listener removed during replay" };
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
if (listeners.indexOf(callback) < 0) {
|
|
1225
|
-
return { success: false, error: "Listener was removed during replay" };
|
|
1226
|
-
}
|
|
1227
|
-
try {
|
|
1228
|
-
const result = callback.onUpdate({
|
|
1229
|
-
type: "update-session",
|
|
1230
|
-
sessionId,
|
|
1231
|
-
metadata: { value: metadata, version: metadataVersion },
|
|
1232
|
-
agentState: { value: agentState, version: agentStateVersion }
|
|
1245
|
+
if (result && typeof result.catch === "function") {
|
|
1246
|
+
result.catch(() => {
|
|
1233
1247
|
});
|
|
1234
|
-
if (result && typeof result.catch === "function") {
|
|
1235
|
-
result.catch(() => {
|
|
1236
|
-
});
|
|
1237
|
-
}
|
|
1238
|
-
} catch {
|
|
1239
1248
|
}
|
|
1240
|
-
|
|
1241
|
-
const result = callback.onUpdate({
|
|
1242
|
-
type: "activity",
|
|
1243
|
-
sessionId,
|
|
1244
|
-
...lastActivity
|
|
1245
|
-
});
|
|
1246
|
-
if (result && typeof result.catch === "function") {
|
|
1247
|
-
result.catch(() => {
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
} catch {
|
|
1251
|
-
}
|
|
1252
|
-
return { success: true, listenerId: listeners.length - 1 };
|
|
1249
|
+
} catch {
|
|
1253
1250
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1251
|
+
return { success: true, listenerId: listeners.length - 1 };
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
const serviceInfo = await server.registerService(serviceDefinition, { overwrite: true });
|
|
1257
1255
|
console.log(`[HYPHA SESSION] Session service registered: ${serviceInfo.id}`);
|
|
1258
1256
|
return {
|
|
1259
1257
|
serviceInfo,
|
|
@@ -1316,6 +1314,13 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
|
|
|
1316
1314
|
removeListener(listener, "disconnect");
|
|
1317
1315
|
}
|
|
1318
1316
|
await server.unregisterService(serviceInfo.id);
|
|
1317
|
+
},
|
|
1318
|
+
reregister: async () => {
|
|
1319
|
+
try {
|
|
1320
|
+
await server.registerService(serviceDefinition, { overwrite: true });
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
if (!String(e?.message).includes("already exists")) throw e;
|
|
1323
|
+
}
|
|
1319
1324
|
}
|
|
1320
1325
|
};
|
|
1321
1326
|
}
|
|
@@ -1796,6 +1801,10 @@ function wrapWithNono(command, args, config) {
|
|
|
1796
1801
|
if (existsSync(realLocalDir)) {
|
|
1797
1802
|
nonoArgs.push("--read", realLocalDir);
|
|
1798
1803
|
}
|
|
1804
|
+
const realKeychainDir = join$1(homedir(), "Library", "Keychains");
|
|
1805
|
+
if (existsSync(realKeychainDir)) {
|
|
1806
|
+
nonoArgs.push("--read", realKeychainDir);
|
|
1807
|
+
}
|
|
1799
1808
|
}
|
|
1800
1809
|
if (config.nonoConfig?.allowDirs) {
|
|
1801
1810
|
for (const dir of config.nonoConfig.allowDirs) {
|
|
@@ -3886,9 +3895,8 @@ async function stageCredentialsForSharing(sessionId) {
|
|
|
3886
3895
|
const realHome = homedir();
|
|
3887
3896
|
const realClaudeDir = join$1(realHome, ".claude");
|
|
3888
3897
|
await mkdir(STAGED_HOMES_DIR, { recursive: true });
|
|
3889
|
-
const tmpHome =
|
|
3890
|
-
|
|
3891
|
-
);
|
|
3898
|
+
const tmpHome = join$1(STAGED_HOMES_DIR, sessionId);
|
|
3899
|
+
await mkdir(tmpHome, { recursive: true });
|
|
3892
3900
|
const stagedClaudeDir = join$1(tmpHome, ".claude");
|
|
3893
3901
|
await mkdir(stagedClaudeDir, { recursive: true });
|
|
3894
3902
|
const credentialFiles = ["credentials.json", ".credentials.json"];
|
|
@@ -3909,10 +3917,12 @@ async function stageCredentialsForSharing(sessionId) {
|
|
|
3909
3917
|
);
|
|
3910
3918
|
} catch {
|
|
3911
3919
|
}
|
|
3912
|
-
const
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3920
|
+
const claudeJsonPath = join$1(tmpHome, ".claude.json");
|
|
3921
|
+
if (!existsSync(claudeJsonPath)) {
|
|
3922
|
+
try {
|
|
3923
|
+
await writeFile(claudeJsonPath, "{}");
|
|
3924
|
+
} catch {
|
|
3925
|
+
}
|
|
3916
3926
|
}
|
|
3917
3927
|
return {
|
|
3918
3928
|
homePath: tmpHome,
|
|
@@ -4154,7 +4164,7 @@ class ProcessSupervisor {
|
|
|
4154
4164
|
async persistSpec(spec) {
|
|
4155
4165
|
const filePath = path.join(this.persistDir, `${spec.id}.json`);
|
|
4156
4166
|
const tmpPath = filePath + ".tmp";
|
|
4157
|
-
await writeFile(tmpPath, JSON.stringify(spec, null, 2), "utf-8");
|
|
4167
|
+
await writeFile$1(tmpPath, JSON.stringify(spec, null, 2), "utf-8");
|
|
4158
4168
|
await rename(tmpPath, filePath);
|
|
4159
4169
|
}
|
|
4160
4170
|
async deleteSpec(id) {
|
|
@@ -5320,14 +5330,22 @@ async function startDaemon(options) {
|
|
|
5320
5330
|
logger.log(`Hypha connection permanently lost: ${reason}`);
|
|
5321
5331
|
requestShutdown("hypha-disconnected", String(reason));
|
|
5322
5332
|
});
|
|
5333
|
+
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
5323
5334
|
server.on("services_registered", () => {
|
|
5324
5335
|
if (consecutiveHeartbeatFailures > 0) {
|
|
5325
5336
|
logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
|
|
5326
5337
|
consecutiveHeartbeatFailures = 0;
|
|
5327
5338
|
lastReconnectAt = Date.now();
|
|
5328
5339
|
}
|
|
5340
|
+
const activeSessions = Array.from(pidToTrackedSession.values()).filter((s) => !s.stopped && s.hyphaService);
|
|
5341
|
+
if (activeSessions.length > 0) {
|
|
5342
|
+
logger.log(`Re-registering ${activeSessions.length} session services after reconnect`);
|
|
5343
|
+
Promise.allSettled(activeSessions.map((s) => s.hyphaService.reregister())).then((results) => {
|
|
5344
|
+
const failed = results.filter((r) => r.status === "rejected").length;
|
|
5345
|
+
if (failed > 0) logger.log(`Warning: ${failed} session service re-registrations failed`);
|
|
5346
|
+
});
|
|
5347
|
+
}
|
|
5329
5348
|
});
|
|
5330
|
-
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
5331
5349
|
const getCurrentChildren = () => {
|
|
5332
5350
|
return Array.from(pidToTrackedSession.values()).map((s) => ({
|
|
5333
5351
|
sessionId: s.svampSessionId || `PID-${s.pid}`,
|
|
@@ -5479,6 +5497,9 @@ async function startDaemon(options) {
|
|
|
5479
5497
|
let sessionWasProcessing = !!options2.wasProcessing;
|
|
5480
5498
|
let lastAssistantText = "";
|
|
5481
5499
|
let spawnHasReceivedInit = false;
|
|
5500
|
+
let startupFailureRetryPending = false;
|
|
5501
|
+
let startupRetryMessage;
|
|
5502
|
+
let startupNonJsonLines = [];
|
|
5482
5503
|
const signalProcessing = (processing) => {
|
|
5483
5504
|
sessionService.sendKeepAlive(processing);
|
|
5484
5505
|
const newState = processing ? "running" : "idle";
|
|
@@ -5529,6 +5550,8 @@ async function startDaemon(options) {
|
|
|
5529
5550
|
let isolationCleanupFiles = [];
|
|
5530
5551
|
const spawnClaude = (initialMessage, meta) => {
|
|
5531
5552
|
const effectiveMeta = { ...lastSpawnMeta, ...meta };
|
|
5553
|
+
startupNonJsonLines = [];
|
|
5554
|
+
startupRetryMessage = initialMessage;
|
|
5532
5555
|
let rawPermissionMode = effectiveMeta.permissionMode || agentConfig.default_permission_mode || currentPermissionMode;
|
|
5533
5556
|
if (options2.forceIsolation || sessionMetadata.sharing?.enabled) {
|
|
5534
5557
|
rawPermissionMode = rawPermissionMode === "default" ? "auto-approve-all" : rawPermissionMode;
|
|
@@ -5724,23 +5747,34 @@ async function startDaemon(options) {
|
|
|
5724
5747
|
if (msg.is_error) {
|
|
5725
5748
|
const resultText = msg.result || "";
|
|
5726
5749
|
logger.error(`[Session ${sessionId}] Claude error (is_error=true, api_ms=${msg.duration_api_ms}): "${resultText}"`);
|
|
5727
|
-
const
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
hint = "\n\nRun `claude login` in your terminal on the machine running the daemon to re-authenticate.";
|
|
5733
|
-
} else if (isResumeIssue) {
|
|
5734
|
-
hint = "\n\nThe conversation history may be corrupted. Try starting a fresh session.";
|
|
5750
|
+
const isStartupFailure = msg.duration_api_ms === 0 && msg.num_turns === 0;
|
|
5751
|
+
if (isStartupFailure && !startupFailureRetryPending) {
|
|
5752
|
+
logger.log(`[Session ${sessionId}] Startup failure detected \u2014 scheduling silent retry without --resume`);
|
|
5753
|
+
startupFailureRetryPending = true;
|
|
5754
|
+
lastErrorMessagePushed = true;
|
|
5735
5755
|
} else {
|
|
5736
|
-
|
|
5756
|
+
const lower = resultText.toLowerCase();
|
|
5757
|
+
const isLoginIssue = lower.includes("login") || lower.includes("logged in") || lower.includes("auth") || lower.includes("api key") || lower.includes("unauthorized");
|
|
5758
|
+
const isResumeIssue = lower.includes("tool_use.name") || lower.includes("invalid_request") || lower.includes("messages.");
|
|
5759
|
+
let hint = "";
|
|
5760
|
+
if (isLoginIssue) {
|
|
5761
|
+
hint = "\n\nRun `claude login` in your terminal on the machine running the daemon to re-authenticate.";
|
|
5762
|
+
} else if (isResumeIssue) {
|
|
5763
|
+
hint = "\n\nThe conversation history may be corrupted. Try starting a fresh session.";
|
|
5764
|
+
} else {
|
|
5765
|
+
hint = "\n\nCheck that the Claude Code CLI is properly installed and configured.";
|
|
5766
|
+
}
|
|
5767
|
+
const displayMsg = resultText || "Claude Code exited with an error.";
|
|
5768
|
+
let contextInfo = "";
|
|
5769
|
+
if (startupNonJsonLines.length > 0) {
|
|
5770
|
+
contextInfo = "\n\n**Startup output:**\n```\n" + startupNonJsonLines.slice(-10).join("\n") + "\n```";
|
|
5771
|
+
}
|
|
5772
|
+
sessionService.pushMessage({
|
|
5773
|
+
type: "assistant",
|
|
5774
|
+
content: [{ type: "text", text: `**Error:** ${displayMsg}${hint}${contextInfo}` }]
|
|
5775
|
+
}, "agent");
|
|
5776
|
+
lastErrorMessagePushed = true;
|
|
5737
5777
|
}
|
|
5738
|
-
const displayMsg = resultText || "Claude Code exited with an error.";
|
|
5739
|
-
sessionService.pushMessage({
|
|
5740
|
-
type: "assistant",
|
|
5741
|
-
content: [{ type: "text", text: `**Error:** ${displayMsg}${hint}` }]
|
|
5742
|
-
}, "agent");
|
|
5743
|
-
lastErrorMessagePushed = true;
|
|
5744
5778
|
}
|
|
5745
5779
|
}
|
|
5746
5780
|
if (msg.type === "result") {
|
|
@@ -5955,6 +5989,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
5955
5989
|
const isResumeFailure = !spawnHasReceivedInit && claudeResumeId && msg.session_id !== claudeResumeId;
|
|
5956
5990
|
const isConversationClear = spawnHasReceivedInit && claudeResumeId && msg.session_id !== claudeResumeId;
|
|
5957
5991
|
spawnHasReceivedInit = true;
|
|
5992
|
+
startupFailureRetryPending = false;
|
|
5958
5993
|
claudeResumeId = msg.session_id;
|
|
5959
5994
|
sessionMetadata = { ...sessionMetadata, claudeSessionId: msg.session_id };
|
|
5960
5995
|
sessionService.updateMetadata(sessionMetadata);
|
|
@@ -5996,6 +6031,9 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
5996
6031
|
}
|
|
5997
6032
|
} catch {
|
|
5998
6033
|
logger.log(`[Session ${sessionId}] Claude stdout (non-JSON): ${line}`);
|
|
6034
|
+
if (!spawnHasReceivedInit) {
|
|
6035
|
+
startupNonJsonLines.push(line.slice(0, 500));
|
|
6036
|
+
}
|
|
5999
6037
|
}
|
|
6000
6038
|
}
|
|
6001
6039
|
});
|
|
@@ -6040,6 +6078,19 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6040
6078
|
sessionMetadata = { ...sessionMetadata, lifecycleState: claudeResumeId ? "idle" : "stopped" };
|
|
6041
6079
|
sessionService.updateMetadata(sessionMetadata);
|
|
6042
6080
|
sessionWasProcessing = false;
|
|
6081
|
+
if (startupFailureRetryPending && !trackedSession.stopped) {
|
|
6082
|
+
startupFailureRetryPending = false;
|
|
6083
|
+
const prevResumeId = claudeResumeId;
|
|
6084
|
+
claudeResumeId = void 0;
|
|
6085
|
+
logger.log(`[Session ${sessionId}] Startup failure \u2014 cleared stale resume ID (was: ${prevResumeId})`);
|
|
6086
|
+
if (startupRetryMessage !== void 0) {
|
|
6087
|
+
logger.log(`[Session ${sessionId}] Retrying startup without --resume`);
|
|
6088
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "running" };
|
|
6089
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
6090
|
+
spawnClaude(startupRetryMessage);
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6043
6094
|
const queueLen = sessionMetadata.messageQueue?.length ?? 0;
|
|
6044
6095
|
if (queueLen > 0 && claudeResumeId && !trackedSession.stopped) {
|
|
6045
6096
|
signalProcessing(false);
|
|
@@ -6298,6 +6349,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6298
6349
|
stopSession(sessionId);
|
|
6299
6350
|
},
|
|
6300
6351
|
onMetadataUpdate: (newMeta) => {
|
|
6352
|
+
const prevRalphLoop = sessionMetadata.ralphLoop;
|
|
6301
6353
|
sessionMetadata = {
|
|
6302
6354
|
...newMeta,
|
|
6303
6355
|
// Daemon drives lifecycleState — don't let frontend overwrite with stale value
|
|
@@ -6305,8 +6357,16 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6305
6357
|
// Preserve claudeSessionId set by 'system init' (frontend may not have it)
|
|
6306
6358
|
...sessionMetadata.claudeSessionId ? { claudeSessionId: sessionMetadata.claudeSessionId } : {},
|
|
6307
6359
|
...sessionMetadata.summary && !newMeta.summary ? { summary: sessionMetadata.summary } : {},
|
|
6308
|
-
...sessionMetadata.sessionLink && !newMeta.sessionLink ? { sessionLink: sessionMetadata.sessionLink } : {}
|
|
6360
|
+
...sessionMetadata.sessionLink && !newMeta.sessionLink ? { sessionLink: sessionMetadata.sessionLink } : {},
|
|
6361
|
+
// Preserve parentSessionId — set at spawn time, frontend may not track it
|
|
6362
|
+
...sessionMetadata.parentSessionId ? { parentSessionId: sessionMetadata.parentSessionId } : {},
|
|
6363
|
+
// Preserve daemon-owned ralphLoop — frontend may send stale snapshot without it,
|
|
6364
|
+
// which would wipe the active loop state and cause the bar to disappear mid-run.
|
|
6365
|
+
...prevRalphLoop ? { ralphLoop: prevRalphLoop } : {}
|
|
6309
6366
|
};
|
|
6367
|
+
if (prevRalphLoop && !newMeta.ralphLoop) {
|
|
6368
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
6369
|
+
}
|
|
6310
6370
|
const queue = newMeta.messageQueue;
|
|
6311
6371
|
if (queue && queue.length > 0 && !sessionWasProcessing && !trackedSession.stopped) {
|
|
6312
6372
|
setTimeout(() => {
|
|
@@ -6691,13 +6751,22 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6691
6751
|
stopSession(sessionId);
|
|
6692
6752
|
},
|
|
6693
6753
|
onMetadataUpdate: (newMeta) => {
|
|
6754
|
+
const prevRalphLoop = sessionMetadata.ralphLoop;
|
|
6694
6755
|
sessionMetadata = {
|
|
6695
6756
|
...newMeta,
|
|
6696
6757
|
// Daemon drives lifecycleState — don't let frontend overwrite with stale value
|
|
6697
6758
|
lifecycleState: sessionMetadata.lifecycleState,
|
|
6698
6759
|
...sessionMetadata.summary && !newMeta.summary ? { summary: sessionMetadata.summary } : {},
|
|
6699
|
-
...sessionMetadata.sessionLink && !newMeta.sessionLink ? { sessionLink: sessionMetadata.sessionLink } : {}
|
|
6760
|
+
...sessionMetadata.sessionLink && !newMeta.sessionLink ? { sessionLink: sessionMetadata.sessionLink } : {},
|
|
6761
|
+
// Preserve parentSessionId — set at spawn time, frontend may not track it
|
|
6762
|
+
...sessionMetadata.parentSessionId ? { parentSessionId: sessionMetadata.parentSessionId } : {},
|
|
6763
|
+
// Preserve daemon-owned ralphLoop — frontend may send stale snapshot without it,
|
|
6764
|
+
// which would wipe the active loop state and cause the bar to disappear mid-run.
|
|
6765
|
+
...prevRalphLoop ? { ralphLoop: prevRalphLoop } : {}
|
|
6700
6766
|
};
|
|
6767
|
+
if (prevRalphLoop && !newMeta.ralphLoop) {
|
|
6768
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
6769
|
+
}
|
|
6701
6770
|
if (acpStopped) return;
|
|
6702
6771
|
const queue = newMeta.messageQueue;
|
|
6703
6772
|
if (queue && queue.length > 0 && sessionMetadata.lifecycleState === "idle") {
|
|
@@ -7347,7 +7416,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7347
7416
|
console.log(` Service: svamp-machine-${machineId}`);
|
|
7348
7417
|
console.log(` Log file: ${logger.logFilePath}`);
|
|
7349
7418
|
const HEARTBEAT_INTERVAL_MS = 1e4;
|
|
7350
|
-
const PING_TIMEOUT_MS =
|
|
7419
|
+
const PING_TIMEOUT_MS = 6e4;
|
|
7351
7420
|
const MAX_FAILURES = 60;
|
|
7352
7421
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
7353
7422
|
let heartbeatRunning = false;
|
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService } from './run-
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService } from './run-C7CEDmD4.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|