@syengup/friday-channel-next 0.1.3 → 0.1.5
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/src/friday-session.js +35 -30
- package/dist/src/http/handlers/device-approve.js +14 -0
- package/dist/src/http/middleware/cors.js +1 -1
- package/dist/src/http/server.js +0 -4
- package/dist/src/session/session-manager.d.ts +0 -7
- package/dist/src/session/session-manager.js +1 -38
- package/package.json +1 -1
- package/src/friday-session.forward-agent.test.ts +64 -36
- package/src/friday-session.ts +32 -30
- package/src/http/handlers/device-approve.ts +17 -0
- package/src/http/middleware/cors.ts +1 -1
- package/src/http/server.ts +0 -5
- package/src/session/session-manager.ts +1 -45
- package/src/http/handlers/sessions-delete.ts +0 -59
|
@@ -420,38 +420,43 @@ export function forwardAgentEventRaw(evt) {
|
|
|
420
420
|
}, ended.deviceId);
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
|
-
// Build sessionUsage:
|
|
424
|
-
if (isTerminalLifecycle) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
423
|
+
// Build sessionUsage: store (cumulative session totals) → llm_output (per-run fallback).
|
|
424
|
+
if (isTerminalLifecycle && getFridayAgentForwardRuntime()) {
|
|
425
|
+
// Defer to let store write complete, then read cumulative totals.
|
|
426
|
+
// llm_output data is per-run; store is cumulative across rounds.
|
|
427
|
+
setTimeout(() => {
|
|
428
|
+
let data = outgoingData;
|
|
429
|
+
const storeUsage = tryReadSessionUsageFromStore(sk);
|
|
430
|
+
const llmUsage = consumeRunUsage(evt.runId);
|
|
431
|
+
const memUsage = buildSessionUsageFromRunMetadata(evt.runId);
|
|
432
|
+
let usage;
|
|
433
|
+
if (storeUsage) {
|
|
434
|
+
// Store provides cumulative session totals. Supplement with
|
|
435
|
+
// fresher model/provider from llm_output when available.
|
|
436
|
+
usage = storeUsage;
|
|
437
|
+
if (llmUsage?.modelId)
|
|
438
|
+
usage.modelId = llmUsage.modelId;
|
|
439
|
+
if (llmUsage?.modelProvider)
|
|
440
|
+
usage.modelProvider = llmUsage.modelProvider;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
// First message in session — store not yet written, fall back
|
|
444
|
+
// to per-run llm_output + RunMetadata.
|
|
445
|
+
usage = mergeUsage(llmUsage, memUsage);
|
|
446
|
+
}
|
|
430
447
|
if (usage) {
|
|
431
|
-
|
|
448
|
+
data = { ...outgoingData, sessionUsage: usage };
|
|
432
449
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
completeAgentEventForward({
|
|
445
|
-
evt,
|
|
446
|
-
sk,
|
|
447
|
-
deviceIdRaw,
|
|
448
|
-
outgoingData: data,
|
|
449
|
-
isTerminalLifecycle: true,
|
|
450
|
-
subagentMeta,
|
|
451
|
-
});
|
|
452
|
-
}, 100);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
450
|
+
completeAgentEventForward({
|
|
451
|
+
evt,
|
|
452
|
+
sk,
|
|
453
|
+
deviceIdRaw,
|
|
454
|
+
outgoingData: data,
|
|
455
|
+
isTerminalLifecycle: true,
|
|
456
|
+
subagentMeta,
|
|
457
|
+
});
|
|
458
|
+
}, 100);
|
|
459
|
+
return;
|
|
455
460
|
}
|
|
456
461
|
completeAgentEventForward({
|
|
457
462
|
evt,
|
|
@@ -45,6 +45,20 @@ export async function handleDeviceApprove(req, res) {
|
|
|
45
45
|
}
|
|
46
46
|
const match = pairing.pending.find((entry) => entry.deviceId.trim().toUpperCase() === normalizedDeviceId);
|
|
47
47
|
if (!match) {
|
|
48
|
+
// Gateway may have already auto-approved the device (e.g. mode="local").
|
|
49
|
+
// Check the paired list before returning 404.
|
|
50
|
+
const pairedDevice = (pairing.paired ?? []).find((entry) => entry.deviceId.trim().toUpperCase() === normalizedDeviceId);
|
|
51
|
+
if (pairedDevice) {
|
|
52
|
+
res.statusCode = 200;
|
|
53
|
+
res.setHeader("Content-Type", "application/json");
|
|
54
|
+
res.end(JSON.stringify({
|
|
55
|
+
ok: true,
|
|
56
|
+
deviceId: normalizedDeviceId,
|
|
57
|
+
alreadyApproved: true,
|
|
58
|
+
approvedAtMs: pairedDevice.approvedAtMs,
|
|
59
|
+
}));
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
48
62
|
res.statusCode = 404;
|
|
49
63
|
res.setHeader("Content-Type", "application/json");
|
|
50
64
|
res.end(JSON.stringify({
|
|
@@ -7,5 +7,5 @@ export function applyCorsHeaders(res) {
|
|
|
7
7
|
return;
|
|
8
8
|
res.setHeader("Access-Control-Allow-Origin", cfg.corsAllowOrigin || "*");
|
|
9
9
|
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Last-Event-ID");
|
|
10
|
-
res.setHeader("Access-Control-Allow-Methods", "GET,POST,
|
|
10
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
|
|
11
11
|
}
|
package/dist/src/http/server.js
CHANGED
|
@@ -11,7 +11,6 @@ import { handleFilesDownload } from "./handlers/files-download.js";
|
|
|
11
11
|
import { handleCancel } from "./handlers/cancel.js";
|
|
12
12
|
import { handleDeviceApprove } from "./handlers/device-approve.js";
|
|
13
13
|
import { handleNodesApprove } from "./handlers/nodes-approve.js";
|
|
14
|
-
import { handleSessionsDelete } from "./handlers/sessions-delete.js";
|
|
15
14
|
import { handleSessionsSettings } from "./handlers/sessions-settings.js";
|
|
16
15
|
import { handleModelsList } from "./handlers/models-list.js";
|
|
17
16
|
import { handleStatus } from "./handlers/status.js";
|
|
@@ -56,9 +55,6 @@ async function handleFridayNextRoute(req, res) {
|
|
|
56
55
|
if (req.method === "POST" && pathname === "/friday-next/nodes-approve") {
|
|
57
56
|
return await handleNodesApprove(req, res);
|
|
58
57
|
}
|
|
59
|
-
if (req.method === "DELETE" && pathname === "/friday-next/sessions") {
|
|
60
|
-
return await handleSessionsDelete(req, res);
|
|
61
|
-
}
|
|
62
58
|
if ((req.method === "PUT" || req.method === "GET") && pathname === "/friday-next/sessions/settings") {
|
|
63
59
|
return await handleSessionsSettings(req, res);
|
|
64
60
|
}
|
|
@@ -4,13 +4,6 @@ export declare function splitModelRef(modelRef: string): {
|
|
|
4
4
|
};
|
|
5
5
|
export declare function toSessionStoreKey(rawSessionKey: string): string;
|
|
6
6
|
export declare function ensureSessionLevels(sessionKey: string, reasoningLevel: string, thinkingLevel: string, historyDir?: string): void;
|
|
7
|
-
export declare function resolveSessionsDir(historyDir?: string): string;
|
|
8
|
-
export interface DeleteSessionResult {
|
|
9
|
-
sessionKey: string;
|
|
10
|
-
sessionId?: string;
|
|
11
|
-
transcriptDeleted?: boolean;
|
|
12
|
-
}
|
|
13
|
-
export declare function deleteFridaySession(sessionKey: string, historyDir?: string): DeleteSessionResult;
|
|
14
7
|
export interface FridaySessionSettings {
|
|
15
8
|
reasoningLevel?: string;
|
|
16
9
|
thinkingLevel?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import os from "node:os";
|
|
3
|
-
import { readFileSync, writeFileSync
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
const FRIDAY_AGENT_ID = "main";
|
|
5
5
|
const SESSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
6
6
|
function deriveOpenClawBaseDir(historyDir) {
|
|
@@ -96,43 +96,6 @@ function upsertSessionEntry(data, fileKey, sessionKey) {
|
|
|
96
96
|
export function ensureSessionLevels(sessionKey, reasoningLevel, thinkingLevel, historyDir) {
|
|
97
97
|
setSessionSettings(sessionKey, { reasoningLevel, thinkingLevel }, historyDir);
|
|
98
98
|
}
|
|
99
|
-
export function resolveSessionsDir(historyDir) {
|
|
100
|
-
const base = deriveOpenClawBaseDir(historyDir);
|
|
101
|
-
return join(base, "agents/main/sessions");
|
|
102
|
-
}
|
|
103
|
-
export function deleteFridaySession(sessionKey, historyDir) {
|
|
104
|
-
const result = { sessionKey };
|
|
105
|
-
const sessionsFile = resolveSessionsFilePath(historyDir);
|
|
106
|
-
const data = readSessionsData(sessionsFile);
|
|
107
|
-
if (!data)
|
|
108
|
-
return result;
|
|
109
|
-
const fileKey = toSessionStoreKey(sessionKey);
|
|
110
|
-
const entry = data[fileKey];
|
|
111
|
-
if (!entry)
|
|
112
|
-
return result;
|
|
113
|
-
const sessionId = typeof entry["sessionId"] === "string" ? entry["sessionId"] : undefined;
|
|
114
|
-
const sessionFilePath = typeof entry["sessionFile"] === "string" ? entry["sessionFile"] : undefined;
|
|
115
|
-
result.sessionId = sessionId;
|
|
116
|
-
if (sessionFilePath) {
|
|
117
|
-
try {
|
|
118
|
-
unlinkSync(sessionFilePath);
|
|
119
|
-
result.transcriptDeleted = true;
|
|
120
|
-
}
|
|
121
|
-
catch { /* gone already */ }
|
|
122
|
-
}
|
|
123
|
-
if (sessionId) {
|
|
124
|
-
const dir = resolveSessionsDir(historyDir);
|
|
125
|
-
for (const suffix of [".trajectory.jsonl", ".trajectory-path.json"]) {
|
|
126
|
-
try {
|
|
127
|
-
unlinkSync(join(dir, `${sessionId}${suffix}`));
|
|
128
|
-
}
|
|
129
|
-
catch { /* optional */ }
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
delete data[fileKey];
|
|
133
|
-
writeSessionsData(sessionsFile, data);
|
|
134
|
-
return result;
|
|
135
|
-
}
|
|
136
99
|
export function setSessionSettings(sessionKey, settings, historyDir) {
|
|
137
100
|
try {
|
|
138
101
|
const sessionsFile = resolveSessionsFilePath(historyDir);
|
package/package.json
CHANGED
|
@@ -200,8 +200,20 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
200
200
|
expect("reasoningPrefixChars" in (payload.data as object)).toBe(false);
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
-
it("builds sessionUsage from
|
|
204
|
-
//
|
|
203
|
+
it("builds sessionUsage from store (cumulative) with llm_output fallback", async () => {
|
|
204
|
+
// No store entry — falls back to llm_output per-run data.
|
|
205
|
+
setFridayAgentForwardRuntime({
|
|
206
|
+
runtime: {
|
|
207
|
+
config: { current: () => ({ session: {} }) },
|
|
208
|
+
agent: {
|
|
209
|
+
session: {
|
|
210
|
+
resolveStorePath: () => "/tmp/sessions.json",
|
|
211
|
+
loadSessionStore: vi.fn(() => ({})),
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
} as never);
|
|
216
|
+
|
|
205
217
|
accumulateRunUsage(runId, { input: 100, output: 50, cacheRead: 10, total: 150 }, "my-model", "openai");
|
|
206
218
|
accumulateRunUsage(runId, { input: 30, output: 10, cacheRead: 0, total: 40 }, "my-model", "openai");
|
|
207
219
|
|
|
@@ -213,15 +225,18 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
213
225
|
data: { phase: "end" },
|
|
214
226
|
});
|
|
215
227
|
|
|
216
|
-
//
|
|
228
|
+
// Deferred 100ms — not broadcast yet.
|
|
229
|
+
expect(sseEmitter.broadcastToRun).not.toHaveBeenCalled();
|
|
230
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 150));
|
|
231
|
+
|
|
217
232
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(1);
|
|
218
233
|
const forwarded = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data;
|
|
219
234
|
expect(forwarded.stream).toBe("lifecycle");
|
|
220
235
|
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<string, unknown>;
|
|
221
236
|
expect(sessionUsage).toBeDefined();
|
|
237
|
+
// llm_output fallback — per-run totals.
|
|
222
238
|
expect(sessionUsage.modelId).toBe("my-model");
|
|
223
239
|
expect(sessionUsage.modelProvider).toBe("openai");
|
|
224
|
-
// Accumulated totals across both API calls.
|
|
225
240
|
expect((sessionUsage.tokens as Record<string, unknown>).input).toBe(130);
|
|
226
241
|
expect((sessionUsage.tokens as Record<string, unknown>).output).toBe(60);
|
|
227
242
|
expect((sessionUsage.tokens as Record<string, unknown>).cacheRead).toBe(10);
|
|
@@ -229,50 +244,63 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
229
244
|
expect((sessionUsage.tokens as Record<string, unknown>).totalFresh).toBe(true);
|
|
230
245
|
});
|
|
231
246
|
|
|
232
|
-
it("
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
+
it("prefers store cumulative totals over llm_output per-run data", async () => {
|
|
248
|
+
const storeKey = toSessionStoreKey(sessionKey);
|
|
249
|
+
setFridayAgentForwardRuntime({
|
|
250
|
+
runtime: {
|
|
251
|
+
config: { current: () => ({ session: {} }) },
|
|
252
|
+
agent: {
|
|
253
|
+
session: {
|
|
254
|
+
resolveStorePath: () => "/tmp/sessions.json",
|
|
255
|
+
loadSessionStore: vi.fn(() => ({
|
|
256
|
+
[storeKey]: {
|
|
257
|
+
model: "store-model",
|
|
258
|
+
modelProvider: "old-provider",
|
|
259
|
+
inputTokens: 5000,
|
|
260
|
+
outputTokens: 2000,
|
|
261
|
+
totalTokens: 99999,
|
|
262
|
+
totalTokensFresh: true,
|
|
263
|
+
contextTokens: 128000,
|
|
264
|
+
estimatedCostUsd: 0.05,
|
|
265
|
+
cacheRead: 100,
|
|
266
|
+
cacheWrite: 50,
|
|
267
|
+
},
|
|
268
|
+
})),
|
|
269
|
+
},
|
|
270
|
+
},
|
|
247
271
|
},
|
|
248
|
-
});
|
|
272
|
+
} as never);
|
|
273
|
+
|
|
274
|
+
// llm_output has fresher model/provider but per-run (smaller) tokens.
|
|
275
|
+
accumulateRunUsage(runId, { input: 500, output: 100, cacheRead: 200, total: 800 }, "llm-model", "llm-provider");
|
|
249
276
|
|
|
250
277
|
forwardAgentEventRaw({
|
|
251
278
|
runId,
|
|
252
|
-
seq:
|
|
279
|
+
seq: 1,
|
|
253
280
|
stream: "lifecycle",
|
|
254
281
|
sessionKey,
|
|
255
282
|
data: { phase: "end" },
|
|
256
283
|
});
|
|
257
284
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
285
|
+
expect(sseEmitter.broadcastToRun).not.toHaveBeenCalled();
|
|
286
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 150));
|
|
287
|
+
|
|
288
|
+
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(1);
|
|
289
|
+
const forwarded = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data;
|
|
290
|
+
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<string, unknown>;
|
|
263
291
|
expect(sessionUsage).toBeDefined();
|
|
264
|
-
//
|
|
265
|
-
expect(sessionUsage
|
|
266
|
-
expect(sessionUsage
|
|
267
|
-
expect((sessionUsage
|
|
268
|
-
|
|
269
|
-
expect(
|
|
270
|
-
expect(
|
|
271
|
-
|
|
272
|
-
expect((sessionUsage
|
|
292
|
+
// Store cumulative totals win.
|
|
293
|
+
expect((sessionUsage.tokens as Record<string, unknown>).input).toBe(5000);
|
|
294
|
+
expect((sessionUsage.tokens as Record<string, unknown>).output).toBe(2000);
|
|
295
|
+
expect((sessionUsage.tokens as Record<string, unknown>).total).toBe(99999);
|
|
296
|
+
// Model/provider from llm_output (fresher) override store.
|
|
297
|
+
expect(sessionUsage.modelId).toBe("llm-model");
|
|
298
|
+
expect(sessionUsage.modelProvider).toBe("llm-provider");
|
|
299
|
+
expect(sessionUsage.estimatedCostUsd).toBe(0.05);
|
|
300
|
+
expect((sessionUsage.context as Record<string, unknown>).windowMax).toBe(128000);
|
|
273
301
|
});
|
|
274
302
|
|
|
275
|
-
it("
|
|
303
|
+
it("uses store cumulative totals when llm_output has no data", async () => {
|
|
276
304
|
const storeKey = toSessionStoreKey(sessionKey);
|
|
277
305
|
const store: Record<string, Record<string, unknown>> = {
|
|
278
306
|
[storeKey]: {
|
package/src/friday-session.ts
CHANGED
|
@@ -483,38 +483,40 @@ export function forwardAgentEventRaw(evt: ForwardAgentEventArgs): void {
|
|
|
483
483
|
}
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
// Build sessionUsage:
|
|
487
|
-
if (isTerminalLifecycle) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const
|
|
486
|
+
// Build sessionUsage: store (cumulative session totals) → llm_output (per-run fallback).
|
|
487
|
+
if (isTerminalLifecycle && getFridayAgentForwardRuntime()) {
|
|
488
|
+
// Defer to let store write complete, then read cumulative totals.
|
|
489
|
+
// llm_output data is per-run; store is cumulative across rounds.
|
|
490
|
+
setTimeout(() => {
|
|
491
|
+
let data = outgoingData;
|
|
492
|
+
const storeUsage = tryReadSessionUsageFromStore(sk);
|
|
493
|
+
const llmUsage = consumeRunUsage(evt.runId);
|
|
494
|
+
const memUsage = buildSessionUsageFromRunMetadata(evt.runId);
|
|
495
|
+
let usage: FridaySessionUsagePayload | undefined;
|
|
496
|
+
if (storeUsage) {
|
|
497
|
+
// Store provides cumulative session totals. Supplement with
|
|
498
|
+
// fresher model/provider from llm_output when available.
|
|
499
|
+
usage = storeUsage;
|
|
500
|
+
if (llmUsage?.modelId) usage.modelId = llmUsage.modelId;
|
|
501
|
+
if (llmUsage?.modelProvider) usage.modelProvider = llmUsage.modelProvider;
|
|
502
|
+
} else {
|
|
503
|
+
// First message in session — store not yet written, fall back
|
|
504
|
+
// to per-run llm_output + RunMetadata.
|
|
505
|
+
usage = mergeUsage(llmUsage, memUsage);
|
|
506
|
+
}
|
|
494
507
|
if (usage) {
|
|
495
|
-
|
|
508
|
+
data = { ...outgoingData, sessionUsage: usage };
|
|
496
509
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
completeAgentEventForward({
|
|
508
|
-
evt,
|
|
509
|
-
sk,
|
|
510
|
-
deviceIdRaw,
|
|
511
|
-
outgoingData: data,
|
|
512
|
-
isTerminalLifecycle: true,
|
|
513
|
-
subagentMeta,
|
|
514
|
-
});
|
|
515
|
-
}, 100);
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
510
|
+
completeAgentEventForward({
|
|
511
|
+
evt,
|
|
512
|
+
sk,
|
|
513
|
+
deviceIdRaw,
|
|
514
|
+
outgoingData: data,
|
|
515
|
+
isTerminalLifecycle: true,
|
|
516
|
+
subagentMeta,
|
|
517
|
+
});
|
|
518
|
+
}, 100);
|
|
519
|
+
return;
|
|
518
520
|
}
|
|
519
521
|
|
|
520
522
|
completeAgentEventForward({
|
|
@@ -59,6 +59,23 @@ export async function handleDeviceApprove(
|
|
|
59
59
|
);
|
|
60
60
|
|
|
61
61
|
if (!match) {
|
|
62
|
+
// Gateway may have already auto-approved the device (e.g. mode="local").
|
|
63
|
+
// Check the paired list before returning 404.
|
|
64
|
+
const pairedDevice = (pairing.paired ?? []).find(
|
|
65
|
+
(entry) => entry.deviceId.trim().toUpperCase() === normalizedDeviceId,
|
|
66
|
+
);
|
|
67
|
+
if (pairedDevice) {
|
|
68
|
+
res.statusCode = 200;
|
|
69
|
+
res.setHeader("Content-Type", "application/json");
|
|
70
|
+
res.end(JSON.stringify({
|
|
71
|
+
ok: true,
|
|
72
|
+
deviceId: normalizedDeviceId,
|
|
73
|
+
alreadyApproved: true,
|
|
74
|
+
approvedAtMs: (pairedDevice as any).approvedAtMs,
|
|
75
|
+
}));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
res.statusCode = 404;
|
|
63
80
|
res.setHeader("Content-Type", "application/json");
|
|
64
81
|
res.end(JSON.stringify({
|
|
@@ -8,5 +8,5 @@ export function applyCorsHeaders(res: ServerResponse): void {
|
|
|
8
8
|
if (!cfg.corsEnabled) return;
|
|
9
9
|
res.setHeader("Access-Control-Allow-Origin", cfg.corsAllowOrigin || "*");
|
|
10
10
|
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Last-Event-ID");
|
|
11
|
-
res.setHeader("Access-Control-Allow-Methods", "GET,POST,
|
|
11
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
|
|
12
12
|
}
|
package/src/http/server.ts
CHANGED
|
@@ -13,7 +13,6 @@ import { handleFilesDownload } from "./handlers/files-download.js";
|
|
|
13
13
|
import { handleCancel } from "./handlers/cancel.js";
|
|
14
14
|
import { handleDeviceApprove } from "./handlers/device-approve.js";
|
|
15
15
|
import { handleNodesApprove } from "./handlers/nodes-approve.js";
|
|
16
|
-
import { handleSessionsDelete } from "./handlers/sessions-delete.js";
|
|
17
16
|
import { handleSessionsSettings } from "./handlers/sessions-settings.js";
|
|
18
17
|
import { handleModelsList } from "./handlers/models-list.js";
|
|
19
18
|
import { handleStatus } from "./handlers/status.js";
|
|
@@ -70,10 +69,6 @@ async function handleFridayNextRoute(
|
|
|
70
69
|
return await handleNodesApprove(req, res);
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
if (req.method === "DELETE" && pathname === "/friday-next/sessions") {
|
|
74
|
-
return await handleSessionsDelete(req, res);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
72
|
if ((req.method === "PUT" || req.method === "GET") && pathname === "/friday-next/sessions/settings") {
|
|
78
73
|
return await handleSessionsSettings(req, res);
|
|
79
74
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import os from "node:os";
|
|
3
|
-
import { readFileSync, writeFileSync
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
|
|
5
5
|
const FRIDAY_AGENT_ID = "main";
|
|
6
6
|
const SESSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
@@ -111,50 +111,6 @@ export function ensureSessionLevels(
|
|
|
111
111
|
setSessionSettings(sessionKey, { reasoningLevel, thinkingLevel }, historyDir);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export function resolveSessionsDir(historyDir?: string): string {
|
|
115
|
-
const base = deriveOpenClawBaseDir(historyDir);
|
|
116
|
-
return join(base, "agents/main/sessions");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface DeleteSessionResult {
|
|
120
|
-
sessionKey: string;
|
|
121
|
-
sessionId?: string;
|
|
122
|
-
transcriptDeleted?: boolean;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function deleteFridaySession(
|
|
126
|
-
sessionKey: string,
|
|
127
|
-
historyDir?: string,
|
|
128
|
-
): DeleteSessionResult {
|
|
129
|
-
const result: DeleteSessionResult = { sessionKey };
|
|
130
|
-
const sessionsFile = resolveSessionsFilePath(historyDir);
|
|
131
|
-
const data = readSessionsData(sessionsFile);
|
|
132
|
-
if (!data) return result;
|
|
133
|
-
|
|
134
|
-
const fileKey = toSessionStoreKey(sessionKey);
|
|
135
|
-
const entry = data[fileKey];
|
|
136
|
-
if (!entry) return result;
|
|
137
|
-
|
|
138
|
-
const sessionId = typeof entry["sessionId"] === "string" ? entry["sessionId"] : undefined;
|
|
139
|
-
const sessionFilePath = typeof entry["sessionFile"] === "string" ? entry["sessionFile"] : undefined;
|
|
140
|
-
result.sessionId = sessionId;
|
|
141
|
-
|
|
142
|
-
if (sessionFilePath) {
|
|
143
|
-
try { unlinkSync(sessionFilePath); result.transcriptDeleted = true; } catch { /* gone already */ }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (sessionId) {
|
|
147
|
-
const dir = resolveSessionsDir(historyDir);
|
|
148
|
-
for (const suffix of [".trajectory.jsonl", ".trajectory-path.json"]) {
|
|
149
|
-
try { unlinkSync(join(dir, `${sessionId}${suffix}`)); } catch { /* optional */ }
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
delete data[fileKey];
|
|
154
|
-
writeSessionsData(sessionsFile, data);
|
|
155
|
-
return result;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
114
|
export interface FridaySessionSettings {
|
|
159
115
|
reasoningLevel?: string;
|
|
160
116
|
thinkingLevel?: string;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import { deleteFridaySession, toSessionStoreKey } from "../../session/session-manager.js";
|
|
3
|
-
import { getActiveRunIds } from "../../agent/active-runs.js";
|
|
4
|
-
import { abortRun } from "../../agent/abort-run.js";
|
|
5
|
-
import { getRunRoute } from "../../run-metadata.js";
|
|
6
|
-
import { sseEmitter } from "../../sse/emitter.js";
|
|
7
|
-
import { readJsonBody } from "../middleware/body.js";
|
|
8
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
9
|
-
|
|
10
|
-
async function cancelActiveRunsForSession(sessionKey: string): Promise<string[]> {
|
|
11
|
-
const storeKey = toSessionStoreKey(sessionKey);
|
|
12
|
-
const cancelled: string[] = [];
|
|
13
|
-
for (const runId of getActiveRunIds()) {
|
|
14
|
-
const route = getRunRoute(runId);
|
|
15
|
-
if (route?.sessionKey === storeKey) {
|
|
16
|
-
await abortRun(runId);
|
|
17
|
-
sseEmitter.untrackRun(runId);
|
|
18
|
-
cancelled.push(runId);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return cancelled;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function handleSessionsDelete(
|
|
25
|
-
req: IncomingMessage,
|
|
26
|
-
res: ServerResponse,
|
|
27
|
-
): Promise<boolean> {
|
|
28
|
-
if (req.method !== "DELETE") {
|
|
29
|
-
res.statusCode = 405;
|
|
30
|
-
res.setHeader("Content-Type", "application/json");
|
|
31
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const token = extractBearerToken(req);
|
|
36
|
-
if (!token) {
|
|
37
|
-
res.statusCode = 401;
|
|
38
|
-
res.setHeader("Content-Type", "application/json");
|
|
39
|
-
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const body = await readJsonBody(req);
|
|
44
|
-
const sessionKey = typeof body?.sessionKey === "string" ? body.sessionKey.trim() : "";
|
|
45
|
-
if (!sessionKey) {
|
|
46
|
-
res.statusCode = 400;
|
|
47
|
-
res.setHeader("Content-Type", "application/json");
|
|
48
|
-
res.end(JSON.stringify({ error: "Missing required field: sessionKey" }));
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const cancelledRuns = await cancelActiveRunsForSession(sessionKey);
|
|
53
|
-
const result = deleteFridaySession(sessionKey);
|
|
54
|
-
|
|
55
|
-
res.statusCode = 200;
|
|
56
|
-
res.setHeader("Content-Type", "application/json");
|
|
57
|
-
res.end(JSON.stringify({ ok: true, ...result, cancelledRuns }));
|
|
58
|
-
return true;
|
|
59
|
-
}
|