@flue/sdk 0.4.1 → 0.5.1
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 +44 -42
- package/dist/app.d.mts +2 -2
- package/dist/app.mjs +2 -2
- package/dist/client.d.mts +7 -3
- package/dist/client.mjs +86 -14
- package/dist/cloudflare/index.d.mts +1 -1
- package/dist/cloudflare/index.mjs +2 -2
- package/dist/{flue-app-CG8i4wNG.d.mts → flue-app-O4_iqLkn.d.mts} +69 -4
- package/dist/{flue-app-DeTOZjPs.mjs → flue-app-SjL4I83Y.mjs} +520 -112
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +56 -6
- package/dist/internal.d.mts +40 -3
- package/dist/internal.mjs +284 -5
- package/dist/{mcp-DM6yv_Qc.mjs → mcp-0-egKS1N.mjs} +59 -38
- package/dist/{mcp-C3UBXVkR.d.mts → mcp-BfcWmA-A.d.mts} +1 -1
- package/dist/node/index.d.mts +1 -1
- package/dist/{providers-DeFRIwp0.mjs → providers-BjEEoKLy.mjs} +11 -1
- package/dist/sandbox.d.mts +1 -1
- package/dist/sandbox.mjs +2 -2
- package/dist/{session-CFOByKnM.mjs → session-BQULcLE9.mjs} +203 -80
- package/dist/{types-BAmV4f3Q.d.mts → types-Cdcq_ET2.d.mts} +87 -32
- package/package.json +2 -1
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { a as buildSkillByPathPrompt, c as createTools, f as resolveSkillFilePath, i as buildSkillByNamePrompt, l as formatBashResult, n as buildPromptText, o as createResultTools, p as skillsDirIn, r as buildResultFollowUpPrompt, s as BUILTIN_TOOL_NAMES, t as ResultUnavailableError } from "./result-K1IRhWKM.mjs";
|
|
2
|
-
import { i as getRegisteredApiKey, r as getProviderConfiguration } from "./providers-
|
|
2
|
+
import { i as getRegisteredApiKey, l as generateOperationId, r as getProviderConfiguration } from "./providers-BjEEoKLy.mjs";
|
|
3
3
|
import { n as createCallHandle, t as abortErrorFor } from "./abort-Bg3qsAkU.mjs";
|
|
4
4
|
import { createFlueFs } from "./sandbox.mjs";
|
|
5
5
|
import { completeSimple, isContextOverflow } from "@mariozechner/pi-ai";
|
|
6
6
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
7
7
|
|
|
8
|
+
//#region src/roles.ts
|
|
9
|
+
function assertRoleExists(roles, roleName) {
|
|
10
|
+
if (!roleName) return;
|
|
11
|
+
if (roles[roleName]) return;
|
|
12
|
+
const available = Object.keys(roles);
|
|
13
|
+
const list = available.length > 0 ? available.join(", ") : "(none defined)";
|
|
14
|
+
throw new Error(`[flue] Role "${roleName}" not registered. Available roles: ${list}. Define roles as markdown files in \`roles/\` (or \`.flue/roles/\` if your root uses the .flue/ source layout).`);
|
|
15
|
+
}
|
|
16
|
+
function resolveEffectiveRole(options) {
|
|
17
|
+
const role = options.callRole ?? options.sessionRole ?? options.agentRole;
|
|
18
|
+
assertRoleExists(options.roles, role);
|
|
19
|
+
return role;
|
|
20
|
+
}
|
|
21
|
+
function resolveRoleModel(roles, roleName) {
|
|
22
|
+
assertRoleExists(roles, roleName);
|
|
23
|
+
return roleName ? roles[roleName]?.model : void 0;
|
|
24
|
+
}
|
|
25
|
+
function resolveRoleThinkingLevel(roles, roleName) {
|
|
26
|
+
assertRoleExists(roles, roleName);
|
|
27
|
+
return roleName ? roles[roleName]?.thinkingLevel : void 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
8
31
|
//#region src/usage.ts
|
|
9
32
|
/** All-zero `PromptUsage`. Identity element for `addUsage`. */
|
|
10
33
|
function emptyUsage() {
|
|
@@ -473,29 +496,6 @@ async function compact(preparation, model, apiKey, signal) {
|
|
|
473
496
|
};
|
|
474
497
|
}
|
|
475
498
|
|
|
476
|
-
//#endregion
|
|
477
|
-
//#region src/roles.ts
|
|
478
|
-
function assertRoleExists(roles, roleName) {
|
|
479
|
-
if (!roleName) return;
|
|
480
|
-
if (roles[roleName]) return;
|
|
481
|
-
const available = Object.keys(roles);
|
|
482
|
-
const list = available.length > 0 ? available.join(", ") : "(none defined)";
|
|
483
|
-
throw new Error(`[flue] Role "${roleName}" not registered. Available roles: ${list}. Define roles as markdown files in \`roles/\` (or \`.flue/roles/\` if your root uses the .flue/ source layout).`);
|
|
484
|
-
}
|
|
485
|
-
function resolveEffectiveRole(options) {
|
|
486
|
-
const role = options.callRole ?? options.sessionRole ?? options.agentRole;
|
|
487
|
-
assertRoleExists(options.roles, role);
|
|
488
|
-
return role;
|
|
489
|
-
}
|
|
490
|
-
function resolveRoleModel(roles, roleName) {
|
|
491
|
-
assertRoleExists(roles, roleName);
|
|
492
|
-
return roleName ? roles[roleName]?.model : void 0;
|
|
493
|
-
}
|
|
494
|
-
function resolveRoleThinkingLevel(roles, roleName) {
|
|
495
|
-
assertRoleExists(roles, roleName);
|
|
496
|
-
return roleName ? roles[roleName]?.thinkingLevel : void 0;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
499
|
//#endregion
|
|
500
500
|
//#region src/session.ts
|
|
501
501
|
const MAX_TASK_DEPTH = 4;
|
|
@@ -652,7 +652,7 @@ var SessionHistory = class SessionHistory {
|
|
|
652
652
|
}
|
|
653
653
|
toData(metadata, createdAt, updatedAt) {
|
|
654
654
|
return {
|
|
655
|
-
version:
|
|
655
|
+
version: 3,
|
|
656
656
|
entries: [...this.entries],
|
|
657
657
|
leafId: this.leafId,
|
|
658
658
|
metadata,
|
|
@@ -703,7 +703,7 @@ function generateEntryId(byId) {
|
|
|
703
703
|
return crypto.randomUUID();
|
|
704
704
|
}
|
|
705
705
|
var Session = class {
|
|
706
|
-
|
|
706
|
+
name;
|
|
707
707
|
fs;
|
|
708
708
|
metadata;
|
|
709
709
|
get role() {
|
|
@@ -723,13 +723,16 @@ var Session = class {
|
|
|
723
723
|
agentTools;
|
|
724
724
|
deleted = false;
|
|
725
725
|
activeOperation;
|
|
726
|
+
activeOperationId;
|
|
727
|
+
toolStartTimes = /* @__PURE__ */ new Map();
|
|
728
|
+
turnStartTime;
|
|
726
729
|
activeTasks = /* @__PURE__ */ new Set();
|
|
727
730
|
sessionRole;
|
|
728
731
|
taskDepth;
|
|
729
732
|
createTaskSession;
|
|
730
733
|
onDelete;
|
|
731
734
|
constructor(options) {
|
|
732
|
-
this.
|
|
735
|
+
this.name = options.name;
|
|
733
736
|
this.storageKey = options.storageKey;
|
|
734
737
|
this.config = options.config;
|
|
735
738
|
this.env = options.env;
|
|
@@ -764,13 +767,15 @@ var Session = class {
|
|
|
764
767
|
},
|
|
765
768
|
getApiKey: (provider) => this.getProviderApiKey(provider),
|
|
766
769
|
onPayload: (payload, model) => this.applyProviderPayloadOverrides(payload, model),
|
|
767
|
-
toolExecution: "parallel"
|
|
770
|
+
toolExecution: "parallel",
|
|
771
|
+
sessionId: options.affinityKey
|
|
768
772
|
});
|
|
769
773
|
this.eventCallback = options.onAgentEvent;
|
|
770
774
|
this.harness.subscribe(async (event) => {
|
|
771
775
|
switch (event.type) {
|
|
772
|
-
case "agent_start":
|
|
773
|
-
|
|
776
|
+
case "agent_start": break;
|
|
777
|
+
case "turn_start":
|
|
778
|
+
this.turnStartTime = Date.now();
|
|
774
779
|
break;
|
|
775
780
|
case "message_update": {
|
|
776
781
|
const aEvent = event.assistantMessageEvent;
|
|
@@ -790,6 +795,7 @@ var Session = class {
|
|
|
790
795
|
break;
|
|
791
796
|
}
|
|
792
797
|
case "tool_execution_start":
|
|
798
|
+
this.toolStartTimes.set(event.toolCallId, Date.now());
|
|
793
799
|
this.emit({
|
|
794
800
|
type: "tool_start",
|
|
795
801
|
toolName: event.toolName,
|
|
@@ -799,16 +805,30 @@ var Session = class {
|
|
|
799
805
|
break;
|
|
800
806
|
case "tool_execution_end":
|
|
801
807
|
this.emit({
|
|
802
|
-
type: "
|
|
808
|
+
type: "tool_call",
|
|
803
809
|
toolName: event.toolName,
|
|
804
810
|
toolCallId: event.toolCallId,
|
|
805
811
|
isError: event.isError,
|
|
806
|
-
result: event.result
|
|
812
|
+
result: event.result,
|
|
813
|
+
durationMs: durationSince(this.toolStartTimes.get(event.toolCallId))
|
|
807
814
|
});
|
|
815
|
+
this.toolStartTimes.delete(event.toolCallId);
|
|
808
816
|
break;
|
|
809
|
-
case "turn_end":
|
|
810
|
-
|
|
817
|
+
case "turn_end": {
|
|
818
|
+
const message = event.message;
|
|
819
|
+
const assistant = message.role === "assistant" ? message : void 0;
|
|
820
|
+
this.emit({
|
|
821
|
+
type: "turn",
|
|
822
|
+
durationMs: durationSince(this.turnStartTime),
|
|
823
|
+
model: assistant?.model,
|
|
824
|
+
usage: fromProviderUsage(assistant?.usage),
|
|
825
|
+
stopReason: assistant?.stopReason,
|
|
826
|
+
isError: assistant?.stopReason === "error" || assistant?.stopReason === "aborted",
|
|
827
|
+
error: assistant?.errorMessage
|
|
828
|
+
});
|
|
829
|
+
this.turnStartTime = void 0;
|
|
811
830
|
break;
|
|
831
|
+
}
|
|
812
832
|
case "agent_end": break;
|
|
813
833
|
}
|
|
814
834
|
});
|
|
@@ -870,6 +890,7 @@ var Session = class {
|
|
|
870
890
|
shell(command, options) {
|
|
871
891
|
return createCallHandle(options?.signal, (signal) => this.runOperation("shell", signal, async () => {
|
|
872
892
|
const toolCallId = crypto.randomUUID();
|
|
893
|
+
const toolStartMs = Date.now();
|
|
873
894
|
const args = { command };
|
|
874
895
|
if (options?.cwd !== void 0) args.cwd = options.cwd;
|
|
875
896
|
if (options?.env !== void 0) args.env = redactEnvValues(options.env);
|
|
@@ -893,11 +914,12 @@ var Session = class {
|
|
|
893
914
|
const toolResult = formatBashResult(shellResult, command);
|
|
894
915
|
await this.appendShellTriple(toolCallId, args, toolResult, false);
|
|
895
916
|
this.emit({
|
|
896
|
-
type: "
|
|
917
|
+
type: "tool_call",
|
|
897
918
|
toolName: "bash",
|
|
898
919
|
toolCallId,
|
|
899
920
|
isError: false,
|
|
900
|
-
result: toolResult
|
|
921
|
+
result: toolResult,
|
|
922
|
+
durationMs: durationSince(toolStartMs)
|
|
901
923
|
});
|
|
902
924
|
return shellResult;
|
|
903
925
|
} catch (error) {
|
|
@@ -913,11 +935,12 @@ var Session = class {
|
|
|
913
935
|
};
|
|
914
936
|
await this.appendShellTriple(toolCallId, args, errResult, true);
|
|
915
937
|
this.emit({
|
|
916
|
-
type: "
|
|
938
|
+
type: "tool_call",
|
|
917
939
|
toolName: "bash",
|
|
918
940
|
toolCallId,
|
|
919
941
|
isError: true,
|
|
920
|
-
result: errResult
|
|
942
|
+
result: errResult,
|
|
943
|
+
durationMs: durationSince(toolStartMs)
|
|
921
944
|
});
|
|
922
945
|
throw error;
|
|
923
946
|
}
|
|
@@ -1070,7 +1093,7 @@ var Session = class {
|
|
|
1070
1093
|
}],
|
|
1071
1094
|
details: {
|
|
1072
1095
|
taskId: result.taskId,
|
|
1073
|
-
|
|
1096
|
+
session: result.session,
|
|
1074
1097
|
messageId: result.messageId,
|
|
1075
1098
|
role: result.role,
|
|
1076
1099
|
cwd: result.cwd
|
|
@@ -1078,6 +1101,46 @@ var Session = class {
|
|
|
1078
1101
|
};
|
|
1079
1102
|
}
|
|
1080
1103
|
async runTask(text, options, signal) {
|
|
1104
|
+
return this.runExclusive("task", async () => {
|
|
1105
|
+
if (signal?.aborted) throw abortErrorFor(signal);
|
|
1106
|
+
this.activeOperationId = generateOperationId();
|
|
1107
|
+
const operationId = this.activeOperationId;
|
|
1108
|
+
const startedAt = Date.now();
|
|
1109
|
+
this.emit({
|
|
1110
|
+
type: "operation_start",
|
|
1111
|
+
operationId,
|
|
1112
|
+
operationKind: "task"
|
|
1113
|
+
});
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await this.runTaskExclusive(text, options, signal);
|
|
1116
|
+
this.emit({
|
|
1117
|
+
type: "operation",
|
|
1118
|
+
operationId,
|
|
1119
|
+
operationKind: "task",
|
|
1120
|
+
durationMs: durationSince(startedAt),
|
|
1121
|
+
isError: false,
|
|
1122
|
+
result: result.output,
|
|
1123
|
+
usage: usageFromResult(result.output)
|
|
1124
|
+
});
|
|
1125
|
+
return result;
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
const surfaced = signal?.aborted ? abortErrorFor(signal) : error;
|
|
1128
|
+
this.emit({
|
|
1129
|
+
type: "operation",
|
|
1130
|
+
operationId,
|
|
1131
|
+
operationKind: "task",
|
|
1132
|
+
durationMs: durationSince(startedAt),
|
|
1133
|
+
isError: true,
|
|
1134
|
+
error: serializeError(surfaced)
|
|
1135
|
+
});
|
|
1136
|
+
throw surfaced;
|
|
1137
|
+
} finally {
|
|
1138
|
+
this.emit({ type: "idle" });
|
|
1139
|
+
this.activeOperationId = void 0;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
async runTaskExclusive(text, options, signal) {
|
|
1081
1144
|
this.assertActive();
|
|
1082
1145
|
if (!this.createTaskSession) throw new Error("[flue] This session cannot create task sessions.");
|
|
1083
1146
|
if (this.taskDepth >= MAX_TASK_DEPTH) throw new Error(`[flue] Maximum task depth (${MAX_TASK_DEPTH}) exceeded.`);
|
|
@@ -1092,19 +1155,20 @@ var Session = class {
|
|
|
1092
1155
|
prompt: text,
|
|
1093
1156
|
role: requestedRole,
|
|
1094
1157
|
cwd: options?.cwd,
|
|
1095
|
-
|
|
1158
|
+
parentSession: this.name
|
|
1096
1159
|
});
|
|
1160
|
+
const taskStartMs = Date.now();
|
|
1097
1161
|
try {
|
|
1098
1162
|
const role = this.resolveEffectiveRole(options?.role);
|
|
1099
1163
|
child = await this.createTaskSession({
|
|
1100
|
-
|
|
1164
|
+
parentSession: this.name,
|
|
1101
1165
|
taskId,
|
|
1102
1166
|
parentEnv: this.env,
|
|
1103
1167
|
cwd: options?.cwd,
|
|
1104
1168
|
role,
|
|
1105
1169
|
depth: this.taskDepth + 1
|
|
1106
1170
|
});
|
|
1107
|
-
await this.recordTaskSession(child.
|
|
1171
|
+
await this.recordTaskSession(child.name, child.storageKey, taskId);
|
|
1108
1172
|
this.activeTasks.add(child);
|
|
1109
1173
|
if (signal) {
|
|
1110
1174
|
abortListener = () => child?.abort();
|
|
@@ -1126,26 +1190,28 @@ var Session = class {
|
|
|
1126
1190
|
output,
|
|
1127
1191
|
text: typeof output?.text === "string" ? output.text : child.getAssistantText(),
|
|
1128
1192
|
taskId,
|
|
1129
|
-
|
|
1193
|
+
session: child.name,
|
|
1130
1194
|
messageId: child.getLatestAssistantMessageId(),
|
|
1131
1195
|
role,
|
|
1132
1196
|
cwd: options?.cwd
|
|
1133
1197
|
};
|
|
1134
1198
|
this.emit({
|
|
1135
|
-
type: "
|
|
1199
|
+
type: "task",
|
|
1136
1200
|
taskId,
|
|
1137
1201
|
isError: false,
|
|
1138
1202
|
result: taskResult.text,
|
|
1139
|
-
|
|
1203
|
+
durationMs: durationSince(taskStartMs),
|
|
1204
|
+
parentSession: this.name
|
|
1140
1205
|
});
|
|
1141
1206
|
return taskResult;
|
|
1142
1207
|
} catch (error) {
|
|
1143
1208
|
this.emit({
|
|
1144
|
-
type: "
|
|
1209
|
+
type: "task",
|
|
1145
1210
|
taskId,
|
|
1146
1211
|
isError: true,
|
|
1147
1212
|
result: getErrorMessage(error),
|
|
1148
|
-
|
|
1213
|
+
durationMs: durationSince(taskStartMs),
|
|
1214
|
+
parentSession: this.name
|
|
1149
1215
|
});
|
|
1150
1216
|
throw error;
|
|
1151
1217
|
} finally {
|
|
@@ -1159,6 +1225,14 @@ var Session = class {
|
|
|
1159
1225
|
async runOperation(operation, signal, fn) {
|
|
1160
1226
|
return this.runExclusive(operation, async () => {
|
|
1161
1227
|
if (signal?.aborted) throw abortErrorFor(signal);
|
|
1228
|
+
this.activeOperationId = generateOperationId();
|
|
1229
|
+
const operationId = this.activeOperationId;
|
|
1230
|
+
const startedAt = Date.now();
|
|
1231
|
+
this.emit({
|
|
1232
|
+
type: "operation_start",
|
|
1233
|
+
operationId,
|
|
1234
|
+
operationKind: operation
|
|
1235
|
+
});
|
|
1162
1236
|
const onAbort = () => {
|
|
1163
1237
|
this.harness.abort();
|
|
1164
1238
|
this.compactionAbortController?.abort(signal?.reason);
|
|
@@ -1166,18 +1240,38 @@ var Session = class {
|
|
|
1166
1240
|
};
|
|
1167
1241
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1168
1242
|
try {
|
|
1169
|
-
|
|
1243
|
+
const result = await fn();
|
|
1244
|
+
this.emit({
|
|
1245
|
+
type: "operation",
|
|
1246
|
+
operationId,
|
|
1247
|
+
operationKind: operation,
|
|
1248
|
+
durationMs: durationSince(startedAt),
|
|
1249
|
+
isError: false,
|
|
1250
|
+
result,
|
|
1251
|
+
usage: usageFromResult(result)
|
|
1252
|
+
});
|
|
1253
|
+
return result;
|
|
1170
1254
|
} catch (error) {
|
|
1171
|
-
|
|
1255
|
+
const surfaced = signal?.aborted ? abortErrorFor(signal) : error;
|
|
1256
|
+
this.emit({
|
|
1257
|
+
type: "operation",
|
|
1258
|
+
operationId,
|
|
1259
|
+
operationKind: operation,
|
|
1260
|
+
durationMs: durationSince(startedAt),
|
|
1261
|
+
isError: true,
|
|
1262
|
+
error: serializeError(surfaced)
|
|
1263
|
+
});
|
|
1264
|
+
throw surfaced;
|
|
1172
1265
|
} finally {
|
|
1173
1266
|
signal?.removeEventListener("abort", onAbort);
|
|
1174
1267
|
this.emit({ type: "idle" });
|
|
1268
|
+
this.activeOperationId = void 0;
|
|
1175
1269
|
}
|
|
1176
1270
|
});
|
|
1177
1271
|
}
|
|
1178
1272
|
async runExclusive(operation, fn) {
|
|
1179
1273
|
this.assertActive();
|
|
1180
|
-
if (this.activeOperation) throw new Error(`[flue] Session "${this.
|
|
1274
|
+
if (this.activeOperation) throw new Error(`[flue] Session "${this.name}" is already running ${this.activeOperation}. Start another session for parallel conversation branches.`);
|
|
1181
1275
|
this.activeOperation = operation;
|
|
1182
1276
|
try {
|
|
1183
1277
|
return await fn();
|
|
@@ -1186,27 +1280,18 @@ var Session = class {
|
|
|
1186
1280
|
}
|
|
1187
1281
|
}
|
|
1188
1282
|
emit(event) {
|
|
1189
|
-
|
|
1283
|
+
const decorated = {
|
|
1190
1284
|
...event,
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1285
|
+
session: event.session ?? this.name
|
|
1286
|
+
};
|
|
1287
|
+
const operationId = event.operationId ?? this.activeOperationId;
|
|
1288
|
+
if (operationId !== void 0) decorated.operationId = operationId;
|
|
1289
|
+
this.eventCallback?.(decorated);
|
|
1193
1290
|
}
|
|
1194
1291
|
assertActive() {
|
|
1195
|
-
if (this.deleted) throw new Error(`[flue] Session "${this.
|
|
1292
|
+
if (this.deleted) throw new Error(`[flue] Session "${this.name}" has been deleted.`);
|
|
1196
1293
|
}
|
|
1197
|
-
/**
|
|
1198
|
-
* Append the three-message conversational triple that represents a
|
|
1199
|
-
* `session.shell()` call in the message history:
|
|
1200
|
-
*
|
|
1201
|
-
* 1. user — out-of-band request to run the command
|
|
1202
|
-
* 2. assistant — synthetic turn whose content is a single bash
|
|
1203
|
-
* tool_use block (matching the shape pi-ai's
|
|
1204
|
-
* providers produce when the LLM itself calls bash)
|
|
1205
|
-
* 3. toolResult — the bash output, keyed to the same toolCallId
|
|
1206
|
-
*
|
|
1207
|
-
* This makes a session.shell() call indistinguishable from an
|
|
1208
|
-
* LLM-issued bash tool call when later turns read the transcript.
|
|
1209
|
-
*/
|
|
1294
|
+
/** Append a `session.shell()` call as an LLM-shaped bash tool exchange. */
|
|
1210
1295
|
async appendShellTriple(toolCallId, args, toolResult, isError) {
|
|
1211
1296
|
const timestamp = Date.now();
|
|
1212
1297
|
const userMessage = {
|
|
@@ -1271,11 +1356,11 @@ var Session = class {
|
|
|
1271
1356
|
if (!this.createdAt) this.createdAt = now;
|
|
1272
1357
|
await this.store.save(this.storageKey, data);
|
|
1273
1358
|
}
|
|
1274
|
-
async recordTaskSession(
|
|
1359
|
+
async recordTaskSession(session, storageKey, taskId) {
|
|
1275
1360
|
const taskSessions = Array.isArray(this.metadata.taskSessions) ? this.metadata.taskSessions : [];
|
|
1276
|
-
if (!taskSessions.some((task) => task?.
|
|
1361
|
+
if (!taskSessions.some((task) => task?.session === session)) {
|
|
1277
1362
|
taskSessions.push({
|
|
1278
|
-
|
|
1363
|
+
session,
|
|
1279
1364
|
taskId,
|
|
1280
1365
|
storageKey
|
|
1281
1366
|
});
|
|
@@ -1295,7 +1380,7 @@ var Session = class {
|
|
|
1295
1380
|
if (isContextOverflow(assistantMessage, contextWindow)) {
|
|
1296
1381
|
if (this.overflowRecoveryAttempted) return;
|
|
1297
1382
|
this.overflowRecoveryAttempted = true;
|
|
1298
|
-
|
|
1383
|
+
this.internalLog("info", "[flue:compaction] Overflow detected, compacting and retrying...");
|
|
1299
1384
|
const messages = this.harness.state.messages;
|
|
1300
1385
|
const lastMsg = messages[messages.length - 1];
|
|
1301
1386
|
if (lastMsg && lastMsg.role === "assistant") {
|
|
@@ -1313,7 +1398,7 @@ var Session = class {
|
|
|
1313
1398
|
if (assistantMessage.stopReason === "error") return;
|
|
1314
1399
|
const contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
1315
1400
|
if (shouldCompact(contextTokens, contextWindow, this.compactionSettings)) {
|
|
1316
|
-
|
|
1401
|
+
this.internalLog("info", `[flue:compaction] Threshold reached — ${contextTokens} tokens used, window ${contextWindow}, reserve ${this.compactionSettings.reserveTokens}, triggering compaction`);
|
|
1317
1402
|
await this.runCompaction("threshold", false);
|
|
1318
1403
|
}
|
|
1319
1404
|
}
|
|
@@ -1327,6 +1412,7 @@ var Session = class {
|
|
|
1327
1412
|
async runCompaction(reason, willRetry) {
|
|
1328
1413
|
this.compactionAbortController = new AbortController();
|
|
1329
1414
|
const messagesBefore = this.harness.state.messages.length;
|
|
1415
|
+
const compactionStartMs = Date.now();
|
|
1330
1416
|
try {
|
|
1331
1417
|
const model = this.harness.state.model;
|
|
1332
1418
|
const contextEntries = this.history.buildContextEntries();
|
|
@@ -1338,15 +1424,15 @@ var Session = class {
|
|
|
1338
1424
|
details: latestCompaction.details
|
|
1339
1425
|
} : void 0);
|
|
1340
1426
|
if (!preparation) {
|
|
1341
|
-
|
|
1427
|
+
this.internalLog("info", "[flue:compaction] Nothing to compact (no valid cut point found)");
|
|
1342
1428
|
return;
|
|
1343
1429
|
}
|
|
1344
1430
|
const firstKeptEntry = contextEntries[preparation.firstKeptIndex]?.entry;
|
|
1345
1431
|
if (!firstKeptEntry || firstKeptEntry.type !== "message") {
|
|
1346
|
-
|
|
1432
|
+
this.internalLog("info", "[flue:compaction] Nothing to compact (first kept message has no entry)");
|
|
1347
1433
|
return;
|
|
1348
1434
|
}
|
|
1349
|
-
|
|
1435
|
+
this.internalLog("info", `[flue:compaction] Summarizing ${preparation.messagesToSummarize.length} messages` + (preparation.isSplitTurn ? ` (split turn: ${preparation.turnPrefixMessages.length} prefix messages)` : "") + `, keeping messages from index ${preparation.firstKeptIndex}`);
|
|
1350
1436
|
const estimatedTokens = preparation.tokensBefore;
|
|
1351
1437
|
this.emit({
|
|
1352
1438
|
type: "compaction_start",
|
|
@@ -1364,18 +1450,20 @@ var Session = class {
|
|
|
1364
1450
|
});
|
|
1365
1451
|
this.harness.state.messages = this.history.buildContext();
|
|
1366
1452
|
const messagesAfter = this.harness.state.messages.length;
|
|
1367
|
-
|
|
1453
|
+
this.internalLog("info", `[flue:compaction] Complete — messages: ${messagesBefore} → ${messagesAfter}, tokens before: ${result.tokensBefore}`);
|
|
1368
1454
|
this.emit({
|
|
1369
|
-
type: "
|
|
1455
|
+
type: "compaction",
|
|
1370
1456
|
messagesBefore,
|
|
1371
|
-
messagesAfter
|
|
1457
|
+
messagesAfter,
|
|
1458
|
+
durationMs: durationSince(compactionStartMs),
|
|
1459
|
+
usage: result.usage
|
|
1372
1460
|
});
|
|
1373
1461
|
await this.save();
|
|
1374
1462
|
if (willRetry) {
|
|
1375
1463
|
const msgs = this.harness.state.messages;
|
|
1376
1464
|
const lastMsg = msgs[msgs.length - 1];
|
|
1377
1465
|
if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") this.harness.state.messages = msgs.slice(0, -1);
|
|
1378
|
-
|
|
1466
|
+
this.internalLog("info", "[flue:compaction] Retrying after overflow recovery...");
|
|
1379
1467
|
const beforeRetry = this.harness.state.messages.length;
|
|
1380
1468
|
await this.harness.continue();
|
|
1381
1469
|
await this.harness.waitForIdle();
|
|
@@ -1383,11 +1471,20 @@ var Session = class {
|
|
|
1383
1471
|
}
|
|
1384
1472
|
} catch (error) {
|
|
1385
1473
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1386
|
-
|
|
1474
|
+
this.internalLog("error", `[flue:compaction] Failed: ${errorMessage}`, { error });
|
|
1387
1475
|
} finally {
|
|
1388
1476
|
this.compactionAbortController = void 0;
|
|
1389
1477
|
}
|
|
1390
1478
|
}
|
|
1479
|
+
internalLog(level, message, attributes) {
|
|
1480
|
+
console.error(message);
|
|
1481
|
+
this.emit({
|
|
1482
|
+
type: "log",
|
|
1483
|
+
level,
|
|
1484
|
+
message,
|
|
1485
|
+
attributes: normalizeLogAttributes(attributes)
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1391
1488
|
throwIfError(context) {
|
|
1392
1489
|
const errorMsg = this.harness.state.errorMessage;
|
|
1393
1490
|
if (errorMsg) throw new Error(`[flue] ${context} failed: ${errorMsg}`);
|
|
@@ -1530,6 +1627,32 @@ async function deleteSessionTree(store, storageKey, seen = /* @__PURE__ */ new S
|
|
|
1530
1627
|
function getErrorMessage(error) {
|
|
1531
1628
|
return error instanceof Error ? error.message : String(error);
|
|
1532
1629
|
}
|
|
1630
|
+
function serializeError(error) {
|
|
1631
|
+
if (error instanceof Error) return {
|
|
1632
|
+
name: error.name,
|
|
1633
|
+
message: error.message
|
|
1634
|
+
};
|
|
1635
|
+
return error;
|
|
1636
|
+
}
|
|
1637
|
+
function normalizeLogAttributes(attributes) {
|
|
1638
|
+
if (!attributes) return void 0;
|
|
1639
|
+
if (!(attributes.error instanceof Error)) return attributes;
|
|
1640
|
+
return {
|
|
1641
|
+
...attributes,
|
|
1642
|
+
error: serializeError(attributes.error)
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function durationSince(start) {
|
|
1646
|
+
return start === void 0 ? 0 : Date.now() - start;
|
|
1647
|
+
}
|
|
1648
|
+
function usageFromResult(result) {
|
|
1649
|
+
if (typeof result !== "object" || result === null) return void 0;
|
|
1650
|
+
const usage = result.usage;
|
|
1651
|
+
return isPromptUsage(usage) ? usage : void 0;
|
|
1652
|
+
}
|
|
1653
|
+
function isPromptUsage(value) {
|
|
1654
|
+
return typeof value === "object" && value !== null && typeof value.input === "number" && typeof value.output === "number" && typeof value.totalTokens === "number";
|
|
1655
|
+
}
|
|
1533
1656
|
function redactEnvValues(env) {
|
|
1534
1657
|
return Object.fromEntries(Object.keys(env).map((key) => [key, "<redacted>"]));
|
|
1535
1658
|
}
|