@digitalforgestudios/openclaw-sulcus 6.6.6 → 7.2.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 +137 -56
- package/hooks.defaults.json +0 -0
- package/index.js +1893 -162
- package/index.ts +403 -25
- package/openclaw.plugin.json +30 -0
- package/package.json +3 -3
- package/wasm/sulcus_wasm.js +0 -0
- package/wasm/sulcus_wasm_bg.wasm +0 -0
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -35,10 +36,10 @@ module.exports = __toCommonJS(index_exports);
|
|
|
35
36
|
var import_node_path = require("node:path");
|
|
36
37
|
var import_node_fs = require("node:fs");
|
|
37
38
|
var https = __toESM(require("node:https"));
|
|
38
|
-
var
|
|
39
|
-
var
|
|
39
|
+
var http2 = __toESM(require("node:http"));
|
|
40
|
+
var import_node_url2 = require("node:url");
|
|
40
41
|
|
|
41
|
-
// node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
|
|
42
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
|
|
42
43
|
var value_exports = {};
|
|
43
44
|
__export(value_exports, {
|
|
44
45
|
HasPropertyKey: () => HasPropertyKey,
|
|
@@ -107,7 +108,7 @@ function IsUndefined(value) {
|
|
|
107
108
|
return value === void 0;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
// node_modules/@sinclair/typebox/build/esm/type/clone/value.mjs
|
|
111
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/clone/value.mjs
|
|
111
112
|
function ArrayType(value) {
|
|
112
113
|
return value.map((value2) => Visit(value2));
|
|
113
114
|
}
|
|
@@ -137,12 +138,12 @@ function Clone(value) {
|
|
|
137
138
|
return Visit(value);
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
// node_modules/@sinclair/typebox/build/esm/type/clone/type.mjs
|
|
141
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/clone/type.mjs
|
|
141
142
|
function CloneType(schema, options) {
|
|
142
143
|
return options === void 0 ? Clone(schema) : Clone({ ...options, ...schema });
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
// node_modules/@sinclair/typebox/build/esm/value/guard/guard.mjs
|
|
146
|
+
// ../../node_modules/@sinclair/typebox/build/esm/value/guard/guard.mjs
|
|
146
147
|
function IsObject2(value) {
|
|
147
148
|
return value !== null && typeof value === "object";
|
|
148
149
|
}
|
|
@@ -156,7 +157,7 @@ function IsNumber2(value) {
|
|
|
156
157
|
return typeof value === "number";
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
// node_modules/@sinclair/typebox/build/esm/system/policy.mjs
|
|
160
|
+
// ../../node_modules/@sinclair/typebox/build/esm/system/policy.mjs
|
|
160
161
|
var TypeSystemPolicy;
|
|
161
162
|
(function(TypeSystemPolicy2) {
|
|
162
163
|
TypeSystemPolicy2.InstanceMode = "default";
|
|
@@ -188,7 +189,7 @@ var TypeSystemPolicy;
|
|
|
188
189
|
TypeSystemPolicy2.IsVoidLike = IsVoidLike;
|
|
189
190
|
})(TypeSystemPolicy || (TypeSystemPolicy = {}));
|
|
190
191
|
|
|
191
|
-
// node_modules/@sinclair/typebox/build/esm/type/create/immutable.mjs
|
|
192
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/create/immutable.mjs
|
|
192
193
|
function ImmutableArray(value) {
|
|
193
194
|
return globalThis.Object.freeze(value).map((value2) => Immutable(value2));
|
|
194
195
|
}
|
|
@@ -215,7 +216,7 @@ function Immutable(value) {
|
|
|
215
216
|
return IsArray(value) ? ImmutableArray(value) : IsDate(value) ? ImmutableDate(value) : IsUint8Array(value) ? ImmutableUint8Array(value) : IsRegExp(value) ? ImmutableRegExp(value) : IsObject(value) ? ImmutableObject(value) : value;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
// node_modules/@sinclair/typebox/build/esm/type/create/type.mjs
|
|
219
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/create/type.mjs
|
|
219
220
|
function CreateType(schema, options) {
|
|
220
221
|
const result = options !== void 0 ? { ...options, ...schema } : schema;
|
|
221
222
|
switch (TypeSystemPolicy.InstanceMode) {
|
|
@@ -228,21 +229,21 @@ function CreateType(schema, options) {
|
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
// node_modules/@sinclair/typebox/build/esm/type/error/error.mjs
|
|
232
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/error/error.mjs
|
|
232
233
|
var TypeBoxError = class extends Error {
|
|
233
234
|
constructor(message) {
|
|
234
235
|
super(message);
|
|
235
236
|
}
|
|
236
237
|
};
|
|
237
238
|
|
|
238
|
-
// node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
|
239
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
|
239
240
|
var TransformKind = /* @__PURE__ */ Symbol.for("TypeBox.Transform");
|
|
240
241
|
var ReadonlyKind = /* @__PURE__ */ Symbol.for("TypeBox.Readonly");
|
|
241
242
|
var OptionalKind = /* @__PURE__ */ Symbol.for("TypeBox.Optional");
|
|
242
243
|
var Hint = /* @__PURE__ */ Symbol.for("TypeBox.Hint");
|
|
243
244
|
var Kind = /* @__PURE__ */ Symbol.for("TypeBox.Kind");
|
|
244
245
|
|
|
245
|
-
// node_modules/@sinclair/typebox/build/esm/type/guard/kind.mjs
|
|
246
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/guard/kind.mjs
|
|
246
247
|
function IsReadonly(value) {
|
|
247
248
|
return IsObject(value) && value[ReadonlyKind] === "Readonly";
|
|
248
249
|
}
|
|
@@ -373,7 +374,7 @@ function IsSchema(value) {
|
|
|
373
374
|
return IsAny(value) || IsArgument(value) || IsArray3(value) || IsBoolean2(value) || IsBigInt2(value) || IsAsyncIterator2(value) || IsComputed(value) || IsConstructor(value) || IsDate2(value) || IsFunction2(value) || IsInteger(value) || IsIntersect(value) || IsIterator2(value) || IsLiteral(value) || IsMappedKey(value) || IsMappedResult(value) || IsNever(value) || IsNot(value) || IsNull2(value) || IsNumber3(value) || IsObject3(value) || IsPromise(value) || IsRecord(value) || IsRef(value) || IsRegExp2(value) || IsString2(value) || IsSymbol2(value) || IsTemplateLiteral(value) || IsThis(value) || IsTuple(value) || IsUndefined3(value) || IsUnion(value) || IsUint8Array2(value) || IsUnknown(value) || IsUnsafe(value) || IsVoid(value) || IsKind(value);
|
|
374
375
|
}
|
|
375
376
|
|
|
376
|
-
// node_modules/@sinclair/typebox/build/esm/type/guard/type.mjs
|
|
377
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/guard/type.mjs
|
|
377
378
|
var type_exports = {};
|
|
378
379
|
__export(type_exports, {
|
|
379
380
|
IsAny: () => IsAny2,
|
|
@@ -665,7 +666,7 @@ function IsSchema2(value) {
|
|
|
665
666
|
return IsObject(value) && (IsAny2(value) || IsArgument2(value) || IsArray4(value) || IsBoolean3(value) || IsBigInt3(value) || IsAsyncIterator3(value) || IsComputed2(value) || IsConstructor2(value) || IsDate3(value) || IsFunction3(value) || IsInteger2(value) || IsIntersect2(value) || IsIterator3(value) || IsLiteral2(value) || IsMappedKey2(value) || IsMappedResult2(value) || IsNever2(value) || IsNot2(value) || IsNull3(value) || IsNumber4(value) || IsObject4(value) || IsPromise2(value) || IsRecord2(value) || IsRef2(value) || IsRegExp3(value) || IsString3(value) || IsSymbol3(value) || IsTemplateLiteral2(value) || IsThis2(value) || IsTuple2(value) || IsUndefined4(value) || IsUnion2(value) || IsUint8Array3(value) || IsUnknown2(value) || IsUnsafe2(value) || IsVoid2(value) || IsKind2(value));
|
|
666
667
|
}
|
|
667
668
|
|
|
668
|
-
// node_modules/@sinclair/typebox/build/esm/type/patterns/patterns.mjs
|
|
669
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/patterns/patterns.mjs
|
|
669
670
|
var PatternBoolean = "(true|false)";
|
|
670
671
|
var PatternNumber = "(0|[1-9][0-9]*)";
|
|
671
672
|
var PatternString = "(.*)";
|
|
@@ -675,7 +676,7 @@ var PatternNumberExact = `^${PatternNumber}$`;
|
|
|
675
676
|
var PatternStringExact = `^${PatternString}$`;
|
|
676
677
|
var PatternNeverExact = `^${PatternNever}$`;
|
|
677
678
|
|
|
678
|
-
// node_modules/@sinclair/typebox/build/esm/type/sets/set.mjs
|
|
679
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/sets/set.mjs
|
|
679
680
|
function SetIncludes(T, S) {
|
|
680
681
|
return T.includes(S);
|
|
681
682
|
}
|
|
@@ -700,32 +701,32 @@ function SetUnionMany(T) {
|
|
|
700
701
|
return Acc;
|
|
701
702
|
}
|
|
702
703
|
|
|
703
|
-
// node_modules/@sinclair/typebox/build/esm/type/any/any.mjs
|
|
704
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/any/any.mjs
|
|
704
705
|
function Any(options) {
|
|
705
706
|
return CreateType({ [Kind]: "Any" }, options);
|
|
706
707
|
}
|
|
707
708
|
|
|
708
|
-
// node_modules/@sinclair/typebox/build/esm/type/array/array.mjs
|
|
709
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/array/array.mjs
|
|
709
710
|
function Array2(items, options) {
|
|
710
711
|
return CreateType({ [Kind]: "Array", type: "array", items }, options);
|
|
711
712
|
}
|
|
712
713
|
|
|
713
|
-
// node_modules/@sinclair/typebox/build/esm/type/argument/argument.mjs
|
|
714
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/argument/argument.mjs
|
|
714
715
|
function Argument(index) {
|
|
715
716
|
return CreateType({ [Kind]: "Argument", index });
|
|
716
717
|
}
|
|
717
718
|
|
|
718
|
-
// node_modules/@sinclair/typebox/build/esm/type/async-iterator/async-iterator.mjs
|
|
719
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/async-iterator/async-iterator.mjs
|
|
719
720
|
function AsyncIterator(items, options) {
|
|
720
721
|
return CreateType({ [Kind]: "AsyncIterator", type: "AsyncIterator", items }, options);
|
|
721
722
|
}
|
|
722
723
|
|
|
723
|
-
// node_modules/@sinclair/typebox/build/esm/type/computed/computed.mjs
|
|
724
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/computed/computed.mjs
|
|
724
725
|
function Computed(target, parameters, options) {
|
|
725
726
|
return CreateType({ [Kind]: "Computed", target, parameters }, options);
|
|
726
727
|
}
|
|
727
728
|
|
|
728
|
-
// node_modules/@sinclair/typebox/build/esm/type/discard/discard.mjs
|
|
729
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/discard/discard.mjs
|
|
729
730
|
function DiscardKey(value, key) {
|
|
730
731
|
const { [key]: _, ...rest } = value;
|
|
731
732
|
return rest;
|
|
@@ -734,12 +735,12 @@ function Discard(value, keys) {
|
|
|
734
735
|
return keys.reduce((acc, key) => DiscardKey(acc, key), value);
|
|
735
736
|
}
|
|
736
737
|
|
|
737
|
-
// node_modules/@sinclair/typebox/build/esm/type/never/never.mjs
|
|
738
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/never/never.mjs
|
|
738
739
|
function Never(options) {
|
|
739
740
|
return CreateType({ [Kind]: "Never", not: {} }, options);
|
|
740
741
|
}
|
|
741
742
|
|
|
742
|
-
// node_modules/@sinclair/typebox/build/esm/type/mapped/mapped-result.mjs
|
|
743
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/mapped/mapped-result.mjs
|
|
743
744
|
function MappedResult(properties) {
|
|
744
745
|
return CreateType({
|
|
745
746
|
[Kind]: "MappedResult",
|
|
@@ -747,22 +748,22 @@ function MappedResult(properties) {
|
|
|
747
748
|
});
|
|
748
749
|
}
|
|
749
750
|
|
|
750
|
-
// node_modules/@sinclair/typebox/build/esm/type/constructor/constructor.mjs
|
|
751
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/constructor/constructor.mjs
|
|
751
752
|
function Constructor(parameters, returns, options) {
|
|
752
753
|
return CreateType({ [Kind]: "Constructor", type: "Constructor", parameters, returns }, options);
|
|
753
754
|
}
|
|
754
755
|
|
|
755
|
-
// node_modules/@sinclair/typebox/build/esm/type/function/function.mjs
|
|
756
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/function/function.mjs
|
|
756
757
|
function Function(parameters, returns, options) {
|
|
757
758
|
return CreateType({ [Kind]: "Function", type: "Function", parameters, returns }, options);
|
|
758
759
|
}
|
|
759
760
|
|
|
760
|
-
// node_modules/@sinclair/typebox/build/esm/type/union/union-create.mjs
|
|
761
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/union/union-create.mjs
|
|
761
762
|
function UnionCreate(T, options) {
|
|
762
763
|
return CreateType({ [Kind]: "Union", anyOf: T }, options);
|
|
763
764
|
}
|
|
764
765
|
|
|
765
|
-
// node_modules/@sinclair/typebox/build/esm/type/union/union-evaluated.mjs
|
|
766
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/union/union-evaluated.mjs
|
|
766
767
|
function IsUnionOptional(types) {
|
|
767
768
|
return types.some((type) => IsOptional(type));
|
|
768
769
|
}
|
|
@@ -780,12 +781,12 @@ function UnionEvaluated(T, options) {
|
|
|
780
781
|
return T.length === 1 ? CreateType(T[0], options) : T.length === 0 ? Never(options) : ResolveUnion(T, options);
|
|
781
782
|
}
|
|
782
783
|
|
|
783
|
-
// node_modules/@sinclair/typebox/build/esm/type/union/union.mjs
|
|
784
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/union/union.mjs
|
|
784
785
|
function Union(types, options) {
|
|
785
786
|
return types.length === 0 ? Never(options) : types.length === 1 ? CreateType(types[0], options) : UnionCreate(types, options);
|
|
786
787
|
}
|
|
787
788
|
|
|
788
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/parse.mjs
|
|
789
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/parse.mjs
|
|
789
790
|
var TemplateLiteralParserError = class extends TypeBoxError {
|
|
790
791
|
};
|
|
791
792
|
function Unescape(pattern) {
|
|
@@ -909,7 +910,7 @@ function TemplateLiteralParseExact(pattern) {
|
|
|
909
910
|
return TemplateLiteralParse(pattern.slice(1, pattern.length - 1));
|
|
910
911
|
}
|
|
911
912
|
|
|
912
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/finite.mjs
|
|
913
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/finite.mjs
|
|
913
914
|
var TemplateLiteralFiniteError = class extends TypeBoxError {
|
|
914
915
|
};
|
|
915
916
|
function IsNumberExpression(expression) {
|
|
@@ -931,7 +932,7 @@ function IsTemplateLiteralFinite(schema) {
|
|
|
931
932
|
return IsTemplateLiteralExpressionFinite(expression);
|
|
932
933
|
}
|
|
933
934
|
|
|
934
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/generate.mjs
|
|
935
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/generate.mjs
|
|
935
936
|
var TemplateLiteralGenerateError = class extends TypeBoxError {
|
|
936
937
|
};
|
|
937
938
|
function* GenerateReduce(buffer) {
|
|
@@ -963,7 +964,7 @@ function TemplateLiteralGenerate(schema) {
|
|
|
963
964
|
return IsTemplateLiteralExpressionFinite(expression) ? [...TemplateLiteralExpressionGenerate(expression)] : [];
|
|
964
965
|
}
|
|
965
966
|
|
|
966
|
-
// node_modules/@sinclair/typebox/build/esm/type/literal/literal.mjs
|
|
967
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/literal/literal.mjs
|
|
967
968
|
function Literal(value, options) {
|
|
968
969
|
return CreateType({
|
|
969
970
|
[Kind]: "Literal",
|
|
@@ -972,27 +973,27 @@ function Literal(value, options) {
|
|
|
972
973
|
}, options);
|
|
973
974
|
}
|
|
974
975
|
|
|
975
|
-
// node_modules/@sinclair/typebox/build/esm/type/boolean/boolean.mjs
|
|
976
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/boolean/boolean.mjs
|
|
976
977
|
function Boolean2(options) {
|
|
977
978
|
return CreateType({ [Kind]: "Boolean", type: "boolean" }, options);
|
|
978
979
|
}
|
|
979
980
|
|
|
980
|
-
// node_modules/@sinclair/typebox/build/esm/type/bigint/bigint.mjs
|
|
981
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/bigint/bigint.mjs
|
|
981
982
|
function BigInt(options) {
|
|
982
983
|
return CreateType({ [Kind]: "BigInt", type: "bigint" }, options);
|
|
983
984
|
}
|
|
984
985
|
|
|
985
|
-
// node_modules/@sinclair/typebox/build/esm/type/number/number.mjs
|
|
986
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/number/number.mjs
|
|
986
987
|
function Number2(options) {
|
|
987
988
|
return CreateType({ [Kind]: "Number", type: "number" }, options);
|
|
988
989
|
}
|
|
989
990
|
|
|
990
|
-
// node_modules/@sinclair/typebox/build/esm/type/string/string.mjs
|
|
991
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/string/string.mjs
|
|
991
992
|
function String2(options) {
|
|
992
993
|
return CreateType({ [Kind]: "String", type: "string" }, options);
|
|
993
994
|
}
|
|
994
995
|
|
|
995
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/syntax.mjs
|
|
996
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/syntax.mjs
|
|
996
997
|
function* FromUnion(syntax) {
|
|
997
998
|
const trim = syntax.trim().replace(/"|'/g, "");
|
|
998
999
|
return trim === "boolean" ? yield Boolean2() : trim === "number" ? yield Number2() : trim === "bigint" ? yield BigInt() : trim === "string" ? yield String2() : yield (() => {
|
|
@@ -1029,7 +1030,7 @@ function TemplateLiteralSyntax(syntax) {
|
|
|
1029
1030
|
return [...FromSyntax(syntax)];
|
|
1030
1031
|
}
|
|
1031
1032
|
|
|
1032
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/pattern.mjs
|
|
1033
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/pattern.mjs
|
|
1033
1034
|
var TemplateLiteralPatternError = class extends TypeBoxError {
|
|
1034
1035
|
};
|
|
1035
1036
|
function Escape(value) {
|
|
@@ -1044,20 +1045,20 @@ function TemplateLiteralPattern(kinds) {
|
|
|
1044
1045
|
return `^${kinds.map((schema) => Visit2(schema, "")).join("")}$`;
|
|
1045
1046
|
}
|
|
1046
1047
|
|
|
1047
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/union.mjs
|
|
1048
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/union.mjs
|
|
1048
1049
|
function TemplateLiteralToUnion(schema) {
|
|
1049
1050
|
const R = TemplateLiteralGenerate(schema);
|
|
1050
1051
|
const L = R.map((S) => Literal(S));
|
|
1051
1052
|
return UnionEvaluated(L);
|
|
1052
1053
|
}
|
|
1053
1054
|
|
|
1054
|
-
// node_modules/@sinclair/typebox/build/esm/type/template-literal/template-literal.mjs
|
|
1055
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/template-literal/template-literal.mjs
|
|
1055
1056
|
function TemplateLiteral(unresolved, options) {
|
|
1056
1057
|
const pattern = IsString(unresolved) ? TemplateLiteralPattern(TemplateLiteralSyntax(unresolved)) : TemplateLiteralPattern(unresolved);
|
|
1057
1058
|
return CreateType({ [Kind]: "TemplateLiteral", type: "string", pattern }, options);
|
|
1058
1059
|
}
|
|
1059
1060
|
|
|
1060
|
-
// node_modules/@sinclair/typebox/build/esm/type/indexed/indexed-property-keys.mjs
|
|
1061
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/indexed/indexed-property-keys.mjs
|
|
1061
1062
|
function FromTemplateLiteral(templateLiteral) {
|
|
1062
1063
|
const keys = TemplateLiteralGenerate(templateLiteral);
|
|
1063
1064
|
return keys.map((key) => key.toString());
|
|
@@ -1075,7 +1076,7 @@ function IndexPropertyKeys(type) {
|
|
|
1075
1076
|
return [...new Set(IsTemplateLiteral(type) ? FromTemplateLiteral(type) : IsUnion(type) ? FromUnion2(type.anyOf) : IsLiteral(type) ? FromLiteral(type.const) : IsNumber3(type) ? ["[number]"] : IsInteger(type) ? ["[number]"] : [])];
|
|
1076
1077
|
}
|
|
1077
1078
|
|
|
1078
|
-
// node_modules/@sinclair/typebox/build/esm/type/indexed/indexed-from-mapped-result.mjs
|
|
1079
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/indexed/indexed-from-mapped-result.mjs
|
|
1079
1080
|
function FromProperties(type, properties, options) {
|
|
1080
1081
|
const result = {};
|
|
1081
1082
|
for (const K2 of Object.getOwnPropertyNames(properties)) {
|
|
@@ -1091,7 +1092,7 @@ function IndexFromMappedResult(type, mappedResult, options) {
|
|
|
1091
1092
|
return MappedResult(properties);
|
|
1092
1093
|
}
|
|
1093
1094
|
|
|
1094
|
-
// node_modules/@sinclair/typebox/build/esm/type/indexed/indexed.mjs
|
|
1095
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/indexed/indexed.mjs
|
|
1095
1096
|
function FromRest(types, key) {
|
|
1096
1097
|
return types.map((type) => IndexFromPropertyKey(type, key));
|
|
1097
1098
|
}
|
|
@@ -1139,7 +1140,7 @@ function Index(type, key, options) {
|
|
|
1139
1140
|
return CreateType(IsSchema(key) ? FromSchema(type, IndexPropertyKeys(key)) : FromSchema(type, key), options);
|
|
1140
1141
|
}
|
|
1141
1142
|
|
|
1142
|
-
// node_modules/@sinclair/typebox/build/esm/type/indexed/indexed-from-mapped-key.mjs
|
|
1143
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/indexed/indexed-from-mapped-key.mjs
|
|
1143
1144
|
function MappedIndexPropertyKey(type, key, options) {
|
|
1144
1145
|
return { [key]: Index(type, [key], Clone(options)) };
|
|
1145
1146
|
}
|
|
@@ -1156,12 +1157,12 @@ function IndexFromMappedKey(type, mappedKey, options) {
|
|
|
1156
1157
|
return MappedResult(properties);
|
|
1157
1158
|
}
|
|
1158
1159
|
|
|
1159
|
-
// node_modules/@sinclair/typebox/build/esm/type/iterator/iterator.mjs
|
|
1160
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/iterator/iterator.mjs
|
|
1160
1161
|
function Iterator(items, options) {
|
|
1161
1162
|
return CreateType({ [Kind]: "Iterator", type: "Iterator", items }, options);
|
|
1162
1163
|
}
|
|
1163
1164
|
|
|
1164
|
-
// node_modules/@sinclair/typebox/build/esm/type/object/object.mjs
|
|
1165
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/object/object.mjs
|
|
1165
1166
|
function RequiredArray(properties) {
|
|
1166
1167
|
return globalThis.Object.keys(properties).filter((key) => !IsOptional(properties[key]));
|
|
1167
1168
|
}
|
|
@@ -1172,12 +1173,12 @@ function _Object(properties, options) {
|
|
|
1172
1173
|
}
|
|
1173
1174
|
var Object2 = _Object;
|
|
1174
1175
|
|
|
1175
|
-
// node_modules/@sinclair/typebox/build/esm/type/promise/promise.mjs
|
|
1176
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/promise/promise.mjs
|
|
1176
1177
|
function Promise2(item, options) {
|
|
1177
1178
|
return CreateType({ [Kind]: "Promise", type: "Promise", item }, options);
|
|
1178
1179
|
}
|
|
1179
1180
|
|
|
1180
|
-
// node_modules/@sinclair/typebox/build/esm/type/readonly/readonly.mjs
|
|
1181
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/readonly/readonly.mjs
|
|
1181
1182
|
function RemoveReadonly(schema) {
|
|
1182
1183
|
return CreateType(Discard(schema, [ReadonlyKind]));
|
|
1183
1184
|
}
|
|
@@ -1192,7 +1193,7 @@ function Readonly(schema, enable) {
|
|
|
1192
1193
|
return IsMappedResult(schema) ? ReadonlyFromMappedResult(schema, F) : ReadonlyWithFlag(schema, F);
|
|
1193
1194
|
}
|
|
1194
1195
|
|
|
1195
|
-
// node_modules/@sinclair/typebox/build/esm/type/readonly/readonly-from-mapped-result.mjs
|
|
1196
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/readonly/readonly-from-mapped-result.mjs
|
|
1196
1197
|
function FromProperties2(K, F) {
|
|
1197
1198
|
const Acc = {};
|
|
1198
1199
|
for (const K2 of globalThis.Object.getOwnPropertyNames(K))
|
|
@@ -1207,12 +1208,12 @@ function ReadonlyFromMappedResult(R, F) {
|
|
|
1207
1208
|
return MappedResult(P);
|
|
1208
1209
|
}
|
|
1209
1210
|
|
|
1210
|
-
// node_modules/@sinclair/typebox/build/esm/type/tuple/tuple.mjs
|
|
1211
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/tuple/tuple.mjs
|
|
1211
1212
|
function Tuple(types, options) {
|
|
1212
1213
|
return CreateType(types.length > 0 ? { [Kind]: "Tuple", type: "array", items: types, additionalItems: false, minItems: types.length, maxItems: types.length } : { [Kind]: "Tuple", type: "array", minItems: types.length, maxItems: types.length }, options);
|
|
1213
1214
|
}
|
|
1214
1215
|
|
|
1215
|
-
// node_modules/@sinclair/typebox/build/esm/type/mapped/mapped.mjs
|
|
1216
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/mapped/mapped.mjs
|
|
1216
1217
|
function FromMappedResult3(K, P) {
|
|
1217
1218
|
return K in P ? FromSchemaType(K, P[K]) : MappedResult(P);
|
|
1218
1219
|
}
|
|
@@ -1267,7 +1268,7 @@ function Mapped(key, map, options) {
|
|
|
1267
1268
|
return Object2(R, options);
|
|
1268
1269
|
}
|
|
1269
1270
|
|
|
1270
|
-
// node_modules/@sinclair/typebox/build/esm/type/optional/optional.mjs
|
|
1271
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/optional/optional.mjs
|
|
1271
1272
|
function RemoveOptional(schema) {
|
|
1272
1273
|
return CreateType(Discard(schema, [OptionalKind]));
|
|
1273
1274
|
}
|
|
@@ -1282,7 +1283,7 @@ function Optional(schema, enable) {
|
|
|
1282
1283
|
return IsMappedResult(schema) ? OptionalFromMappedResult(schema, F) : OptionalWithFlag(schema, F);
|
|
1283
1284
|
}
|
|
1284
1285
|
|
|
1285
|
-
// node_modules/@sinclair/typebox/build/esm/type/optional/optional-from-mapped-result.mjs
|
|
1286
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/optional/optional-from-mapped-result.mjs
|
|
1286
1287
|
function FromProperties4(P, F) {
|
|
1287
1288
|
const Acc = {};
|
|
1288
1289
|
for (const K2 of globalThis.Object.getOwnPropertyNames(P))
|
|
@@ -1297,14 +1298,14 @@ function OptionalFromMappedResult(R, F) {
|
|
|
1297
1298
|
return MappedResult(P);
|
|
1298
1299
|
}
|
|
1299
1300
|
|
|
1300
|
-
// node_modules/@sinclair/typebox/build/esm/type/intersect/intersect-create.mjs
|
|
1301
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intersect/intersect-create.mjs
|
|
1301
1302
|
function IntersectCreate(T, options = {}) {
|
|
1302
1303
|
const allObjects = T.every((schema) => IsObject3(schema));
|
|
1303
1304
|
const clonedUnevaluatedProperties = IsSchema(options.unevaluatedProperties) ? { unevaluatedProperties: options.unevaluatedProperties } : {};
|
|
1304
1305
|
return CreateType(options.unevaluatedProperties === false || IsSchema(options.unevaluatedProperties) || allObjects ? { ...clonedUnevaluatedProperties, [Kind]: "Intersect", type: "object", allOf: T } : { ...clonedUnevaluatedProperties, [Kind]: "Intersect", allOf: T }, options);
|
|
1305
1306
|
}
|
|
1306
1307
|
|
|
1307
|
-
// node_modules/@sinclair/typebox/build/esm/type/intersect/intersect-evaluated.mjs
|
|
1308
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intersect/intersect-evaluated.mjs
|
|
1308
1309
|
function IsIntersectOptional(types) {
|
|
1309
1310
|
return types.every((left) => IsOptional(left));
|
|
1310
1311
|
}
|
|
@@ -1327,7 +1328,7 @@ function IntersectEvaluated(types, options = {}) {
|
|
|
1327
1328
|
return ResolveIntersect(types, options);
|
|
1328
1329
|
}
|
|
1329
1330
|
|
|
1330
|
-
// node_modules/@sinclair/typebox/build/esm/type/intersect/intersect.mjs
|
|
1331
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intersect/intersect.mjs
|
|
1331
1332
|
function Intersect(types, options) {
|
|
1332
1333
|
if (types.length === 1)
|
|
1333
1334
|
return CreateType(types[0], options);
|
|
@@ -1338,7 +1339,7 @@ function Intersect(types, options) {
|
|
|
1338
1339
|
return IntersectCreate(types, options);
|
|
1339
1340
|
}
|
|
1340
1341
|
|
|
1341
|
-
// node_modules/@sinclair/typebox/build/esm/type/ref/ref.mjs
|
|
1342
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/ref/ref.mjs
|
|
1342
1343
|
function Ref(...args) {
|
|
1343
1344
|
const [$ref, options] = typeof args[0] === "string" ? [args[0], args[1]] : [args[0].$id, args[1]];
|
|
1344
1345
|
if (typeof $ref !== "string")
|
|
@@ -1346,7 +1347,7 @@ function Ref(...args) {
|
|
|
1346
1347
|
return CreateType({ [Kind]: "Ref", $ref }, options);
|
|
1347
1348
|
}
|
|
1348
1349
|
|
|
1349
|
-
// node_modules/@sinclair/typebox/build/esm/type/awaited/awaited.mjs
|
|
1350
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/awaited/awaited.mjs
|
|
1350
1351
|
function FromComputed(target, parameters) {
|
|
1351
1352
|
return Computed("Awaited", [Computed(target, parameters)]);
|
|
1352
1353
|
}
|
|
@@ -1369,7 +1370,7 @@ function Awaited(type, options) {
|
|
|
1369
1370
|
return CreateType(IsComputed(type) ? FromComputed(type.target, type.parameters) : IsIntersect(type) ? FromIntersect2(type.allOf) : IsUnion(type) ? FromUnion4(type.anyOf) : IsPromise(type) ? FromPromise(type.item) : IsRef(type) ? FromRef(type.$ref) : type, options);
|
|
1370
1371
|
}
|
|
1371
1372
|
|
|
1372
|
-
// node_modules/@sinclair/typebox/build/esm/type/keyof/keyof-property-keys.mjs
|
|
1373
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/keyof/keyof-property-keys.mjs
|
|
1373
1374
|
function FromRest4(types) {
|
|
1374
1375
|
const result = [];
|
|
1375
1376
|
for (const L of types)
|
|
@@ -1408,7 +1409,7 @@ function KeyOfPropertyKeys(type) {
|
|
|
1408
1409
|
}
|
|
1409
1410
|
var includePatternProperties = false;
|
|
1410
1411
|
|
|
1411
|
-
// node_modules/@sinclair/typebox/build/esm/type/keyof/keyof.mjs
|
|
1412
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/keyof/keyof.mjs
|
|
1412
1413
|
function FromComputed2(target, parameters) {
|
|
1413
1414
|
return Computed("KeyOf", [Computed(target, parameters)]);
|
|
1414
1415
|
}
|
|
@@ -1428,7 +1429,7 @@ function KeyOf(type, options) {
|
|
|
1428
1429
|
return IsComputed(type) ? FromComputed2(type.target, type.parameters) : IsRef(type) ? FromRef2(type.$ref) : IsMappedResult(type) ? KeyOfFromMappedResult(type, options) : KeyOfFromType(type, options);
|
|
1429
1430
|
}
|
|
1430
1431
|
|
|
1431
|
-
// node_modules/@sinclair/typebox/build/esm/type/keyof/keyof-from-mapped-result.mjs
|
|
1432
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/keyof/keyof-from-mapped-result.mjs
|
|
1432
1433
|
function FromProperties6(properties, options) {
|
|
1433
1434
|
const result = {};
|
|
1434
1435
|
for (const K2 of globalThis.Object.getOwnPropertyNames(properties))
|
|
@@ -1443,7 +1444,7 @@ function KeyOfFromMappedResult(mappedResult, options) {
|
|
|
1443
1444
|
return MappedResult(properties);
|
|
1444
1445
|
}
|
|
1445
1446
|
|
|
1446
|
-
// node_modules/@sinclair/typebox/build/esm/type/composite/composite.mjs
|
|
1447
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/composite/composite.mjs
|
|
1447
1448
|
function CompositeKeys(T) {
|
|
1448
1449
|
const Acc = [];
|
|
1449
1450
|
for (const L of T)
|
|
@@ -1473,37 +1474,37 @@ function Composite(T, options) {
|
|
|
1473
1474
|
return R;
|
|
1474
1475
|
}
|
|
1475
1476
|
|
|
1476
|
-
// node_modules/@sinclair/typebox/build/esm/type/date/date.mjs
|
|
1477
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/date/date.mjs
|
|
1477
1478
|
function Date2(options) {
|
|
1478
1479
|
return CreateType({ [Kind]: "Date", type: "Date" }, options);
|
|
1479
1480
|
}
|
|
1480
1481
|
|
|
1481
|
-
// node_modules/@sinclair/typebox/build/esm/type/null/null.mjs
|
|
1482
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/null/null.mjs
|
|
1482
1483
|
function Null(options) {
|
|
1483
1484
|
return CreateType({ [Kind]: "Null", type: "null" }, options);
|
|
1484
1485
|
}
|
|
1485
1486
|
|
|
1486
|
-
// node_modules/@sinclair/typebox/build/esm/type/symbol/symbol.mjs
|
|
1487
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/symbol/symbol.mjs
|
|
1487
1488
|
function Symbol2(options) {
|
|
1488
1489
|
return CreateType({ [Kind]: "Symbol", type: "symbol" }, options);
|
|
1489
1490
|
}
|
|
1490
1491
|
|
|
1491
|
-
// node_modules/@sinclair/typebox/build/esm/type/undefined/undefined.mjs
|
|
1492
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/undefined/undefined.mjs
|
|
1492
1493
|
function Undefined(options) {
|
|
1493
1494
|
return CreateType({ [Kind]: "Undefined", type: "undefined" }, options);
|
|
1494
1495
|
}
|
|
1495
1496
|
|
|
1496
|
-
// node_modules/@sinclair/typebox/build/esm/type/uint8array/uint8array.mjs
|
|
1497
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/uint8array/uint8array.mjs
|
|
1497
1498
|
function Uint8Array2(options) {
|
|
1498
1499
|
return CreateType({ [Kind]: "Uint8Array", type: "Uint8Array" }, options);
|
|
1499
1500
|
}
|
|
1500
1501
|
|
|
1501
|
-
// node_modules/@sinclair/typebox/build/esm/type/unknown/unknown.mjs
|
|
1502
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/unknown/unknown.mjs
|
|
1502
1503
|
function Unknown(options) {
|
|
1503
1504
|
return CreateType({ [Kind]: "Unknown" }, options);
|
|
1504
1505
|
}
|
|
1505
1506
|
|
|
1506
|
-
// node_modules/@sinclair/typebox/build/esm/type/const/const.mjs
|
|
1507
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/const/const.mjs
|
|
1507
1508
|
function FromArray3(T) {
|
|
1508
1509
|
return T.map((L) => FromValue(L, false));
|
|
1509
1510
|
}
|
|
@@ -1523,12 +1524,12 @@ function Const(T, options) {
|
|
|
1523
1524
|
return CreateType(FromValue(T, true), options);
|
|
1524
1525
|
}
|
|
1525
1526
|
|
|
1526
|
-
// node_modules/@sinclair/typebox/build/esm/type/constructor-parameters/constructor-parameters.mjs
|
|
1527
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/constructor-parameters/constructor-parameters.mjs
|
|
1527
1528
|
function ConstructorParameters(schema, options) {
|
|
1528
1529
|
return IsConstructor(schema) ? Tuple(schema.parameters, options) : Never(options);
|
|
1529
1530
|
}
|
|
1530
1531
|
|
|
1531
|
-
// node_modules/@sinclair/typebox/build/esm/type/enum/enum.mjs
|
|
1532
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/enum/enum.mjs
|
|
1532
1533
|
function Enum(item, options) {
|
|
1533
1534
|
if (IsUndefined(item))
|
|
1534
1535
|
throw new Error("Enum undefined or empty");
|
|
@@ -1538,7 +1539,7 @@ function Enum(item, options) {
|
|
|
1538
1539
|
return Union(anyOf, { ...options, [Hint]: "Enum" });
|
|
1539
1540
|
}
|
|
1540
1541
|
|
|
1541
|
-
// node_modules/@sinclair/typebox/build/esm/type/extends/extends-check.mjs
|
|
1542
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extends/extends-check.mjs
|
|
1542
1543
|
var ExtendsResolverError = class extends TypeBoxError {
|
|
1543
1544
|
};
|
|
1544
1545
|
var ExtendsResult;
|
|
@@ -1789,7 +1790,7 @@ function ExtendsCheck(left, right) {
|
|
|
1789
1790
|
return Visit3(left, right);
|
|
1790
1791
|
}
|
|
1791
1792
|
|
|
1792
|
-
// node_modules/@sinclair/typebox/build/esm/type/extends/extends-from-mapped-result.mjs
|
|
1793
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extends/extends-from-mapped-result.mjs
|
|
1793
1794
|
function FromProperties8(P, Right, True, False, options) {
|
|
1794
1795
|
const Acc = {};
|
|
1795
1796
|
for (const K2 of globalThis.Object.getOwnPropertyNames(P))
|
|
@@ -1804,7 +1805,7 @@ function ExtendsFromMappedResult(Left, Right, True, False, options) {
|
|
|
1804
1805
|
return MappedResult(P);
|
|
1805
1806
|
}
|
|
1806
1807
|
|
|
1807
|
-
// node_modules/@sinclair/typebox/build/esm/type/extends/extends.mjs
|
|
1808
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extends/extends.mjs
|
|
1808
1809
|
function ExtendsResolve(left, right, trueType, falseType) {
|
|
1809
1810
|
const R = ExtendsCheck(left, right);
|
|
1810
1811
|
return R === ExtendsResult.Union ? Union([trueType, falseType]) : R === ExtendsResult.True ? trueType : falseType;
|
|
@@ -1813,7 +1814,7 @@ function Extends(L, R, T, F, options) {
|
|
|
1813
1814
|
return IsMappedResult(L) ? ExtendsFromMappedResult(L, R, T, F, options) : IsMappedKey(L) ? CreateType(ExtendsFromMappedKey(L, R, T, F, options)) : CreateType(ExtendsResolve(L, R, T, F), options);
|
|
1814
1815
|
}
|
|
1815
1816
|
|
|
1816
|
-
// node_modules/@sinclair/typebox/build/esm/type/extends/extends-from-mapped-key.mjs
|
|
1817
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extends/extends-from-mapped-key.mjs
|
|
1817
1818
|
function FromPropertyKey(K, U, L, R, options) {
|
|
1818
1819
|
return {
|
|
1819
1820
|
[K]: Extends(Literal(K), U, L, R, Clone(options))
|
|
@@ -1832,12 +1833,12 @@ function ExtendsFromMappedKey(T, U, L, R, options) {
|
|
|
1832
1833
|
return MappedResult(P);
|
|
1833
1834
|
}
|
|
1834
1835
|
|
|
1835
|
-
// node_modules/@sinclair/typebox/build/esm/type/exclude/exclude-from-template-literal.mjs
|
|
1836
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/exclude/exclude-from-template-literal.mjs
|
|
1836
1837
|
function ExcludeFromTemplateLiteral(L, R) {
|
|
1837
1838
|
return Exclude(TemplateLiteralToUnion(L), R);
|
|
1838
1839
|
}
|
|
1839
1840
|
|
|
1840
|
-
// node_modules/@sinclair/typebox/build/esm/type/exclude/exclude.mjs
|
|
1841
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/exclude/exclude.mjs
|
|
1841
1842
|
function ExcludeRest(L, R) {
|
|
1842
1843
|
const excluded = L.filter((inner) => ExtendsCheck(inner, R) === ExtendsResult.False);
|
|
1843
1844
|
return excluded.length === 1 ? excluded[0] : Union(excluded);
|
|
@@ -1850,7 +1851,7 @@ function Exclude(L, R, options = {}) {
|
|
|
1850
1851
|
return CreateType(IsUnion(L) ? ExcludeRest(L.anyOf, R) : ExtendsCheck(L, R) !== ExtendsResult.False ? Never() : L, options);
|
|
1851
1852
|
}
|
|
1852
1853
|
|
|
1853
|
-
// node_modules/@sinclair/typebox/build/esm/type/exclude/exclude-from-mapped-result.mjs
|
|
1854
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/exclude/exclude-from-mapped-result.mjs
|
|
1854
1855
|
function FromProperties9(P, U) {
|
|
1855
1856
|
const Acc = {};
|
|
1856
1857
|
for (const K2 of globalThis.Object.getOwnPropertyNames(P))
|
|
@@ -1865,12 +1866,12 @@ function ExcludeFromMappedResult(R, T) {
|
|
|
1865
1866
|
return MappedResult(P);
|
|
1866
1867
|
}
|
|
1867
1868
|
|
|
1868
|
-
// node_modules/@sinclair/typebox/build/esm/type/extract/extract-from-template-literal.mjs
|
|
1869
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extract/extract-from-template-literal.mjs
|
|
1869
1870
|
function ExtractFromTemplateLiteral(L, R) {
|
|
1870
1871
|
return Extract(TemplateLiteralToUnion(L), R);
|
|
1871
1872
|
}
|
|
1872
1873
|
|
|
1873
|
-
// node_modules/@sinclair/typebox/build/esm/type/extract/extract.mjs
|
|
1874
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extract/extract.mjs
|
|
1874
1875
|
function ExtractRest(L, R) {
|
|
1875
1876
|
const extracted = L.filter((inner) => ExtendsCheck(inner, R) !== ExtendsResult.False);
|
|
1876
1877
|
return extracted.length === 1 ? extracted[0] : Union(extracted);
|
|
@@ -1883,7 +1884,7 @@ function Extract(L, R, options) {
|
|
|
1883
1884
|
return CreateType(IsUnion(L) ? ExtractRest(L.anyOf, R) : ExtendsCheck(L, R) !== ExtendsResult.False ? L : Never(), options);
|
|
1884
1885
|
}
|
|
1885
1886
|
|
|
1886
|
-
// node_modules/@sinclair/typebox/build/esm/type/extract/extract-from-mapped-result.mjs
|
|
1887
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/extract/extract-from-mapped-result.mjs
|
|
1887
1888
|
function FromProperties10(P, T) {
|
|
1888
1889
|
const Acc = {};
|
|
1889
1890
|
for (const K2 of globalThis.Object.getOwnPropertyNames(P))
|
|
@@ -1898,17 +1899,17 @@ function ExtractFromMappedResult(R, T) {
|
|
|
1898
1899
|
return MappedResult(P);
|
|
1899
1900
|
}
|
|
1900
1901
|
|
|
1901
|
-
// node_modules/@sinclair/typebox/build/esm/type/instance-type/instance-type.mjs
|
|
1902
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/instance-type/instance-type.mjs
|
|
1902
1903
|
function InstanceType(schema, options) {
|
|
1903
1904
|
return IsConstructor(schema) ? CreateType(schema.returns, options) : Never(options);
|
|
1904
1905
|
}
|
|
1905
1906
|
|
|
1906
|
-
// node_modules/@sinclair/typebox/build/esm/type/readonly-optional/readonly-optional.mjs
|
|
1907
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/readonly-optional/readonly-optional.mjs
|
|
1907
1908
|
function ReadonlyOptional(schema) {
|
|
1908
1909
|
return Readonly(Optional(schema));
|
|
1909
1910
|
}
|
|
1910
1911
|
|
|
1911
|
-
// node_modules/@sinclair/typebox/build/esm/type/record/record.mjs
|
|
1912
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/record/record.mjs
|
|
1912
1913
|
function RecordCreateFromPattern(pattern, T, options) {
|
|
1913
1914
|
return CreateType({ [Kind]: "Record", type: "object", patternProperties: { [pattern]: T } }, options);
|
|
1914
1915
|
}
|
|
@@ -1963,7 +1964,7 @@ function RecordValue2(type) {
|
|
|
1963
1964
|
return type.patternProperties[RecordPattern(type)];
|
|
1964
1965
|
}
|
|
1965
1966
|
|
|
1966
|
-
// node_modules/@sinclair/typebox/build/esm/type/instantiate/instantiate.mjs
|
|
1967
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/instantiate/instantiate.mjs
|
|
1967
1968
|
function FromConstructor2(args, type) {
|
|
1968
1969
|
type.parameters = FromTypes(args, type.parameters);
|
|
1969
1970
|
type.returns = FromType(args, type.returns);
|
|
@@ -2038,12 +2039,12 @@ function Instantiate(type, args) {
|
|
|
2038
2039
|
return FromType(args, CloneType(type));
|
|
2039
2040
|
}
|
|
2040
2041
|
|
|
2041
|
-
// node_modules/@sinclair/typebox/build/esm/type/integer/integer.mjs
|
|
2042
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/integer/integer.mjs
|
|
2042
2043
|
function Integer(options) {
|
|
2043
2044
|
return CreateType({ [Kind]: "Integer", type: "integer" }, options);
|
|
2044
2045
|
}
|
|
2045
2046
|
|
|
2046
|
-
// node_modules/@sinclair/typebox/build/esm/type/intrinsic/intrinsic-from-mapped-key.mjs
|
|
2047
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intrinsic/intrinsic-from-mapped-key.mjs
|
|
2047
2048
|
function MappedIntrinsicPropertyKey(K, M, options) {
|
|
2048
2049
|
return {
|
|
2049
2050
|
[K]: Intrinsic(Literal(K), M, Clone(options))
|
|
@@ -2063,7 +2064,7 @@ function IntrinsicFromMappedKey(T, M, options) {
|
|
|
2063
2064
|
return MappedResult(P);
|
|
2064
2065
|
}
|
|
2065
2066
|
|
|
2066
|
-
// node_modules/@sinclair/typebox/build/esm/type/intrinsic/intrinsic.mjs
|
|
2067
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intrinsic/intrinsic.mjs
|
|
2067
2068
|
function ApplyUncapitalize(value) {
|
|
2068
2069
|
const [first, rest] = [value.slice(0, 1), value.slice(1)];
|
|
2069
2070
|
return [first.toLowerCase(), rest].join("");
|
|
@@ -2108,27 +2109,27 @@ function Intrinsic(schema, mode, options = {}) {
|
|
|
2108
2109
|
);
|
|
2109
2110
|
}
|
|
2110
2111
|
|
|
2111
|
-
// node_modules/@sinclair/typebox/build/esm/type/intrinsic/capitalize.mjs
|
|
2112
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intrinsic/capitalize.mjs
|
|
2112
2113
|
function Capitalize(T, options = {}) {
|
|
2113
2114
|
return Intrinsic(T, "Capitalize", options);
|
|
2114
2115
|
}
|
|
2115
2116
|
|
|
2116
|
-
// node_modules/@sinclair/typebox/build/esm/type/intrinsic/lowercase.mjs
|
|
2117
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intrinsic/lowercase.mjs
|
|
2117
2118
|
function Lowercase(T, options = {}) {
|
|
2118
2119
|
return Intrinsic(T, "Lowercase", options);
|
|
2119
2120
|
}
|
|
2120
2121
|
|
|
2121
|
-
// node_modules/@sinclair/typebox/build/esm/type/intrinsic/uncapitalize.mjs
|
|
2122
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intrinsic/uncapitalize.mjs
|
|
2122
2123
|
function Uncapitalize(T, options = {}) {
|
|
2123
2124
|
return Intrinsic(T, "Uncapitalize", options);
|
|
2124
2125
|
}
|
|
2125
2126
|
|
|
2126
|
-
// node_modules/@sinclair/typebox/build/esm/type/intrinsic/uppercase.mjs
|
|
2127
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/intrinsic/uppercase.mjs
|
|
2127
2128
|
function Uppercase(T, options = {}) {
|
|
2128
2129
|
return Intrinsic(T, "Uppercase", options);
|
|
2129
2130
|
}
|
|
2130
2131
|
|
|
2131
|
-
// node_modules/@sinclair/typebox/build/esm/type/omit/omit-from-mapped-result.mjs
|
|
2132
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/omit/omit-from-mapped-result.mjs
|
|
2132
2133
|
function FromProperties12(properties, propertyKeys, options) {
|
|
2133
2134
|
const result = {};
|
|
2134
2135
|
for (const K2 of globalThis.Object.getOwnPropertyNames(properties))
|
|
@@ -2143,7 +2144,7 @@ function OmitFromMappedResult(mappedResult, propertyKeys, options) {
|
|
|
2143
2144
|
return MappedResult(properties);
|
|
2144
2145
|
}
|
|
2145
2146
|
|
|
2146
|
-
// node_modules/@sinclair/typebox/build/esm/type/omit/omit.mjs
|
|
2147
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/omit/omit.mjs
|
|
2147
2148
|
function FromIntersect6(types, propertyKeys) {
|
|
2148
2149
|
return types.map((type) => OmitResolve(type, propertyKeys));
|
|
2149
2150
|
}
|
|
@@ -2177,7 +2178,7 @@ function Omit(type, key, options) {
|
|
|
2177
2178
|
return IsMappedResult(type) ? OmitFromMappedResult(type, propertyKeys, options) : IsMappedKey(key) ? OmitFromMappedKey(type, key, options) : isTypeRef && isKeyRef ? Computed("Omit", [type, typeKey], options) : !isTypeRef && isKeyRef ? Computed("Omit", [type, typeKey], options) : isTypeRef && !isKeyRef ? Computed("Omit", [type, typeKey], options) : CreateType({ ...OmitResolve(type, propertyKeys), ...options });
|
|
2178
2179
|
}
|
|
2179
2180
|
|
|
2180
|
-
// node_modules/@sinclair/typebox/build/esm/type/omit/omit-from-mapped-key.mjs
|
|
2181
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/omit/omit-from-mapped-key.mjs
|
|
2181
2182
|
function FromPropertyKey2(type, key, options) {
|
|
2182
2183
|
return { [key]: Omit(type, [key], Clone(options)) };
|
|
2183
2184
|
}
|
|
@@ -2194,7 +2195,7 @@ function OmitFromMappedKey(type, mappedKey, options) {
|
|
|
2194
2195
|
return MappedResult(properties);
|
|
2195
2196
|
}
|
|
2196
2197
|
|
|
2197
|
-
// node_modules/@sinclair/typebox/build/esm/type/pick/pick-from-mapped-result.mjs
|
|
2198
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/pick/pick-from-mapped-result.mjs
|
|
2198
2199
|
function FromProperties14(properties, propertyKeys, options) {
|
|
2199
2200
|
const result = {};
|
|
2200
2201
|
for (const K2 of globalThis.Object.getOwnPropertyNames(properties))
|
|
@@ -2209,7 +2210,7 @@ function PickFromMappedResult(mappedResult, propertyKeys, options) {
|
|
|
2209
2210
|
return MappedResult(properties);
|
|
2210
2211
|
}
|
|
2211
2212
|
|
|
2212
|
-
// node_modules/@sinclair/typebox/build/esm/type/pick/pick.mjs
|
|
2213
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/pick/pick.mjs
|
|
2213
2214
|
function FromIntersect7(types, propertyKeys) {
|
|
2214
2215
|
return types.map((type) => PickResolve(type, propertyKeys));
|
|
2215
2216
|
}
|
|
@@ -2243,7 +2244,7 @@ function Pick(type, key, options) {
|
|
|
2243
2244
|
return IsMappedResult(type) ? PickFromMappedResult(type, propertyKeys, options) : IsMappedKey(key) ? PickFromMappedKey(type, key, options) : isTypeRef && isKeyRef ? Computed("Pick", [type, typeKey], options) : !isTypeRef && isKeyRef ? Computed("Pick", [type, typeKey], options) : isTypeRef && !isKeyRef ? Computed("Pick", [type, typeKey], options) : CreateType({ ...PickResolve(type, propertyKeys), ...options });
|
|
2244
2245
|
}
|
|
2245
2246
|
|
|
2246
|
-
// node_modules/@sinclair/typebox/build/esm/type/pick/pick-from-mapped-key.mjs
|
|
2247
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/pick/pick-from-mapped-key.mjs
|
|
2247
2248
|
function FromPropertyKey3(type, key, options) {
|
|
2248
2249
|
return {
|
|
2249
2250
|
[key]: Pick(type, [key], Clone(options))
|
|
@@ -2262,7 +2263,7 @@ function PickFromMappedKey(type, mappedKey, options) {
|
|
|
2262
2263
|
return MappedResult(properties);
|
|
2263
2264
|
}
|
|
2264
2265
|
|
|
2265
|
-
// node_modules/@sinclair/typebox/build/esm/type/partial/partial.mjs
|
|
2266
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/partial/partial.mjs
|
|
2266
2267
|
function FromComputed3(target, parameters) {
|
|
2267
2268
|
return Computed("Partial", [Computed(target, parameters)]);
|
|
2268
2269
|
}
|
|
@@ -2303,7 +2304,7 @@ function Partial(type, options) {
|
|
|
2303
2304
|
}
|
|
2304
2305
|
}
|
|
2305
2306
|
|
|
2306
|
-
// node_modules/@sinclair/typebox/build/esm/type/partial/partial-from-mapped-result.mjs
|
|
2307
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/partial/partial-from-mapped-result.mjs
|
|
2307
2308
|
function FromProperties17(K, options) {
|
|
2308
2309
|
const Acc = {};
|
|
2309
2310
|
for (const K2 of globalThis.Object.getOwnPropertyNames(K))
|
|
@@ -2318,7 +2319,7 @@ function PartialFromMappedResult(R, options) {
|
|
|
2318
2319
|
return MappedResult(P);
|
|
2319
2320
|
}
|
|
2320
2321
|
|
|
2321
|
-
// node_modules/@sinclair/typebox/build/esm/type/required/required.mjs
|
|
2322
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/required/required.mjs
|
|
2322
2323
|
function FromComputed4(target, parameters) {
|
|
2323
2324
|
return Computed("Required", [Computed(target, parameters)]);
|
|
2324
2325
|
}
|
|
@@ -2359,7 +2360,7 @@ function Required(type, options) {
|
|
|
2359
2360
|
}
|
|
2360
2361
|
}
|
|
2361
2362
|
|
|
2362
|
-
// node_modules/@sinclair/typebox/build/esm/type/required/required-from-mapped-result.mjs
|
|
2363
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/required/required-from-mapped-result.mjs
|
|
2363
2364
|
function FromProperties19(P, options) {
|
|
2364
2365
|
const Acc = {};
|
|
2365
2366
|
for (const K2 of globalThis.Object.getOwnPropertyNames(P))
|
|
@@ -2374,7 +2375,7 @@ function RequiredFromMappedResult(R, options) {
|
|
|
2374
2375
|
return MappedResult(P);
|
|
2375
2376
|
}
|
|
2376
2377
|
|
|
2377
|
-
// node_modules/@sinclair/typebox/build/esm/type/module/compute.mjs
|
|
2378
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/module/compute.mjs
|
|
2378
2379
|
function DereferenceParameters(moduleProperties, types) {
|
|
2379
2380
|
return types.map((type) => {
|
|
2380
2381
|
return IsRef(type) ? Dereference(moduleProperties, type.$ref) : FromType2(moduleProperties, type);
|
|
@@ -2470,7 +2471,7 @@ function ComputeModuleProperties(moduleProperties) {
|
|
|
2470
2471
|
}, {});
|
|
2471
2472
|
}
|
|
2472
2473
|
|
|
2473
|
-
// node_modules/@sinclair/typebox/build/esm/type/module/module.mjs
|
|
2474
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/module/module.mjs
|
|
2474
2475
|
var TModule = class {
|
|
2475
2476
|
constructor($defs) {
|
|
2476
2477
|
const computed = ComputeModuleProperties($defs);
|
|
@@ -2493,17 +2494,17 @@ function Module(properties) {
|
|
|
2493
2494
|
return new TModule(properties);
|
|
2494
2495
|
}
|
|
2495
2496
|
|
|
2496
|
-
// node_modules/@sinclair/typebox/build/esm/type/not/not.mjs
|
|
2497
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/not/not.mjs
|
|
2497
2498
|
function Not(type, options) {
|
|
2498
2499
|
return CreateType({ [Kind]: "Not", not: type }, options);
|
|
2499
2500
|
}
|
|
2500
2501
|
|
|
2501
|
-
// node_modules/@sinclair/typebox/build/esm/type/parameters/parameters.mjs
|
|
2502
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/parameters/parameters.mjs
|
|
2502
2503
|
function Parameters(schema, options) {
|
|
2503
2504
|
return IsFunction2(schema) ? Tuple(schema.parameters, options) : Never();
|
|
2504
2505
|
}
|
|
2505
2506
|
|
|
2506
|
-
// node_modules/@sinclair/typebox/build/esm/type/recursive/recursive.mjs
|
|
2507
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/recursive/recursive.mjs
|
|
2507
2508
|
var Ordinal = 0;
|
|
2508
2509
|
function Recursive(callback, options = {}) {
|
|
2509
2510
|
if (IsUndefined(options.$id))
|
|
@@ -2513,13 +2514,13 @@ function Recursive(callback, options = {}) {
|
|
|
2513
2514
|
return CreateType({ [Hint]: "Recursive", ...thisType }, options);
|
|
2514
2515
|
}
|
|
2515
2516
|
|
|
2516
|
-
// node_modules/@sinclair/typebox/build/esm/type/regexp/regexp.mjs
|
|
2517
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/regexp/regexp.mjs
|
|
2517
2518
|
function RegExp2(unresolved, options) {
|
|
2518
2519
|
const expr = IsString(unresolved) ? new globalThis.RegExp(unresolved) : unresolved;
|
|
2519
2520
|
return CreateType({ [Kind]: "RegExp", type: "RegExp", source: expr.source, flags: expr.flags }, options);
|
|
2520
2521
|
}
|
|
2521
2522
|
|
|
2522
|
-
// node_modules/@sinclair/typebox/build/esm/type/rest/rest.mjs
|
|
2523
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/rest/rest.mjs
|
|
2523
2524
|
function RestResolve(T) {
|
|
2524
2525
|
return IsIntersect(T) ? T.allOf : IsUnion(T) ? T.anyOf : IsTuple(T) ? T.items ?? [] : [];
|
|
2525
2526
|
}
|
|
@@ -2527,12 +2528,12 @@ function Rest(T) {
|
|
|
2527
2528
|
return RestResolve(T);
|
|
2528
2529
|
}
|
|
2529
2530
|
|
|
2530
|
-
// node_modules/@sinclair/typebox/build/esm/type/return-type/return-type.mjs
|
|
2531
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/return-type/return-type.mjs
|
|
2531
2532
|
function ReturnType(schema, options) {
|
|
2532
2533
|
return IsFunction2(schema) ? CreateType(schema.returns, options) : Never(options);
|
|
2533
2534
|
}
|
|
2534
2535
|
|
|
2535
|
-
// node_modules/@sinclair/typebox/build/esm/type/transform/transform.mjs
|
|
2536
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/transform/transform.mjs
|
|
2536
2537
|
var TransformDecodeBuilder = class {
|
|
2537
2538
|
constructor(schema) {
|
|
2538
2539
|
this.schema = schema;
|
|
@@ -2564,17 +2565,17 @@ function Transform(schema) {
|
|
|
2564
2565
|
return new TransformDecodeBuilder(schema);
|
|
2565
2566
|
}
|
|
2566
2567
|
|
|
2567
|
-
// node_modules/@sinclair/typebox/build/esm/type/unsafe/unsafe.mjs
|
|
2568
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/unsafe/unsafe.mjs
|
|
2568
2569
|
function Unsafe(options = {}) {
|
|
2569
2570
|
return CreateType({ [Kind]: options[Kind] ?? "Unsafe" }, options);
|
|
2570
2571
|
}
|
|
2571
2572
|
|
|
2572
|
-
// node_modules/@sinclair/typebox/build/esm/type/void/void.mjs
|
|
2573
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/void/void.mjs
|
|
2573
2574
|
function Void(options) {
|
|
2574
2575
|
return CreateType({ [Kind]: "Void", type: "void" }, options);
|
|
2575
2576
|
}
|
|
2576
2577
|
|
|
2577
|
-
// node_modules/@sinclair/typebox/build/esm/type/type/type.mjs
|
|
2578
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/type/type.mjs
|
|
2578
2579
|
var type_exports2 = {};
|
|
2579
2580
|
__export(type_exports2, {
|
|
2580
2581
|
Any: () => Any,
|
|
@@ -2641,9 +2642,1453 @@ __export(type_exports2, {
|
|
|
2641
2642
|
Void: () => Void
|
|
2642
2643
|
});
|
|
2643
2644
|
|
|
2644
|
-
// node_modules/@sinclair/typebox/build/esm/type/type/index.mjs
|
|
2645
|
+
// ../../node_modules/@sinclair/typebox/build/esm/type/type/index.mjs
|
|
2645
2646
|
var Type = type_exports2;
|
|
2646
2647
|
|
|
2648
|
+
// src/local-client.ts
|
|
2649
|
+
var http = __toESM(require("node:http"));
|
|
2650
|
+
var import_node_url = require("node:url");
|
|
2651
|
+
var SulcusLocalClient = class {
|
|
2652
|
+
endpoint;
|
|
2653
|
+
apiKey;
|
|
2654
|
+
timeoutMs;
|
|
2655
|
+
maxConsecutiveFailures;
|
|
2656
|
+
cooldownMs;
|
|
2657
|
+
logger;
|
|
2658
|
+
// Health tracking
|
|
2659
|
+
consecutiveFailures = 0;
|
|
2660
|
+
lastFailureAt = 0;
|
|
2661
|
+
lastSuccessAt = 0;
|
|
2662
|
+
constructor(opts) {
|
|
2663
|
+
this.endpoint = opts.endpoint.replace(/\/+$/, "");
|
|
2664
|
+
this.apiKey = opts.apiKey ?? "";
|
|
2665
|
+
this.timeoutMs = opts.timeoutMs ?? 2e3;
|
|
2666
|
+
this.maxConsecutiveFailures = opts.maxConsecutiveFailures ?? 3;
|
|
2667
|
+
this.cooldownMs = opts.cooldownMs ?? 3e4;
|
|
2668
|
+
this.logger = opts.logger ?? { info: () => {
|
|
2669
|
+
}, warn: () => {
|
|
2670
|
+
}, debug: () => {
|
|
2671
|
+
} };
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Whether the local sidecar is considered available.
|
|
2675
|
+
* False if too many consecutive failures and cooldown hasn't elapsed.
|
|
2676
|
+
*/
|
|
2677
|
+
isAvailable() {
|
|
2678
|
+
if (this.consecutiveFailures < this.maxConsecutiveFailures) return true;
|
|
2679
|
+
if (Date.now() - this.lastFailureAt > this.cooldownMs) return true;
|
|
2680
|
+
return false;
|
|
2681
|
+
}
|
|
2682
|
+
/** Reset health state (e.g. on config change or manual intervention). */
|
|
2683
|
+
resetHealth() {
|
|
2684
|
+
this.consecutiveFailures = 0;
|
|
2685
|
+
this.lastFailureAt = 0;
|
|
2686
|
+
}
|
|
2687
|
+
/** Mark a successful request. */
|
|
2688
|
+
markSuccess() {
|
|
2689
|
+
this.consecutiveFailures = 0;
|
|
2690
|
+
this.lastSuccessAt = Date.now();
|
|
2691
|
+
}
|
|
2692
|
+
/** Mark a failed request. */
|
|
2693
|
+
markFailure() {
|
|
2694
|
+
this.consecutiveFailures++;
|
|
2695
|
+
this.lastFailureAt = Date.now();
|
|
2696
|
+
}
|
|
2697
|
+
/** Health summary for diagnostics. */
|
|
2698
|
+
healthSummary() {
|
|
2699
|
+
return {
|
|
2700
|
+
available: this.isAvailable(),
|
|
2701
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
2702
|
+
lastSuccessAt: this.lastSuccessAt,
|
|
2703
|
+
lastFailureAt: this.lastFailureAt
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
// ── HTTP transport ──────────────────────────────────────────────────────
|
|
2707
|
+
/**
|
|
2708
|
+
* Make an HTTP request to the local sidecar. No retries — fail fast.
|
|
2709
|
+
*/
|
|
2710
|
+
request(method, path, body) {
|
|
2711
|
+
if (!this.isAvailable()) {
|
|
2712
|
+
return Promise.reject(new Error(`sulcus-local: sidecar unavailable (${this.consecutiveFailures} consecutive failures, cooldown active)`));
|
|
2713
|
+
}
|
|
2714
|
+
let parsedUrl;
|
|
2715
|
+
try {
|
|
2716
|
+
parsedUrl = new import_node_url.URL(this.endpoint + path);
|
|
2717
|
+
} catch (e) {
|
|
2718
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2719
|
+
return Promise.reject(new Error(`sulcus-local: invalid URL ${this.endpoint}${path}: ${msg}`));
|
|
2720
|
+
}
|
|
2721
|
+
const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
2722
|
+
return new Promise((resolve2, reject) => {
|
|
2723
|
+
const headers = {
|
|
2724
|
+
Accept: "application/json"
|
|
2725
|
+
};
|
|
2726
|
+
if (this.apiKey) {
|
|
2727
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2728
|
+
}
|
|
2729
|
+
if (bodyStr !== void 0) {
|
|
2730
|
+
headers["Content-Type"] = "application/json";
|
|
2731
|
+
headers["Content-Length"] = String(Buffer.byteLength(bodyStr));
|
|
2732
|
+
}
|
|
2733
|
+
const req = http.request(
|
|
2734
|
+
{
|
|
2735
|
+
hostname: parsedUrl.hostname,
|
|
2736
|
+
port: parsedUrl.port ? parseInt(parsedUrl.port, 10) : 80,
|
|
2737
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
2738
|
+
method,
|
|
2739
|
+
headers,
|
|
2740
|
+
timeout: this.timeoutMs
|
|
2741
|
+
},
|
|
2742
|
+
(res) => {
|
|
2743
|
+
const chunks = [];
|
|
2744
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
2745
|
+
res.on("end", () => {
|
|
2746
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2747
|
+
if (!res.statusCode || res.statusCode >= 400) {
|
|
2748
|
+
this.markFailure();
|
|
2749
|
+
return reject(new Error(`sulcus-local: HTTP ${res.statusCode} for ${method} ${path}: ${raw.substring(0, 200)}`));
|
|
2750
|
+
}
|
|
2751
|
+
this.markSuccess();
|
|
2752
|
+
if (!raw || raw.trim() === "") return resolve2(null);
|
|
2753
|
+
try {
|
|
2754
|
+
resolve2(JSON.parse(raw));
|
|
2755
|
+
} catch {
|
|
2756
|
+
resolve2(raw);
|
|
2757
|
+
}
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
);
|
|
2761
|
+
req.on("timeout", () => {
|
|
2762
|
+
req.destroy();
|
|
2763
|
+
this.markFailure();
|
|
2764
|
+
reject(new Error(`sulcus-local: timeout (${this.timeoutMs}ms) for ${method} ${path}`));
|
|
2765
|
+
});
|
|
2766
|
+
req.on("error", (e) => {
|
|
2767
|
+
this.markFailure();
|
|
2768
|
+
reject(new Error(`sulcus-local: network error for ${method} ${path}: ${e.message}`));
|
|
2769
|
+
});
|
|
2770
|
+
if (bodyStr !== void 0) req.write(bodyStr);
|
|
2771
|
+
req.end();
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
// ── API methods (mirror SulcusCloudClient's interface) ──────────────────
|
|
2775
|
+
/**
|
|
2776
|
+
* Search memory by semantic query.
|
|
2777
|
+
* Uses the same `/api/v1/agent/search` endpoint as the cloud client.
|
|
2778
|
+
*/
|
|
2779
|
+
async search_memory(query, limit, namespace) {
|
|
2780
|
+
const body = { query };
|
|
2781
|
+
if (limit !== void 0) body.limit = limit;
|
|
2782
|
+
if (namespace !== void 0) body.namespace = namespace;
|
|
2783
|
+
const res = await this.request("POST", "/api/v1/agent/search", body);
|
|
2784
|
+
const results = res?.results ?? res?.items ?? res?.nodes ?? (Array.isArray(res) ? res : []);
|
|
2785
|
+
return { results };
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* Store a new memory node.
|
|
2789
|
+
* Uses the same `/api/v1/agent/nodes` endpoint as the cloud client.
|
|
2790
|
+
*/
|
|
2791
|
+
async add_memory(content, memoryType, hints) {
|
|
2792
|
+
const body = { label: content };
|
|
2793
|
+
if (memoryType) body.memory_type = memoryType;
|
|
2794
|
+
if (hints) body.extraction_hints = hints;
|
|
2795
|
+
const res = await this.request("POST", "/api/v1/agent/nodes", body);
|
|
2796
|
+
return res ?? { id: "unknown" };
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Get a single memory node by ID.
|
|
2800
|
+
*/
|
|
2801
|
+
async get_memory(id) {
|
|
2802
|
+
try {
|
|
2803
|
+
const res = await this.request("GET", `/api/v1/agent/nodes/${id}`);
|
|
2804
|
+
return res;
|
|
2805
|
+
} catch {
|
|
2806
|
+
return null;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Update an existing memory node.
|
|
2811
|
+
*/
|
|
2812
|
+
async update_memory(id, updates) {
|
|
2813
|
+
const res = await this.request("PATCH", `/api/v1/agent/nodes/${id}`, updates);
|
|
2814
|
+
return res;
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* List hot nodes (most active memories).
|
|
2818
|
+
*/
|
|
2819
|
+
async list_hot_nodes(limit) {
|
|
2820
|
+
const q = limit ? `?limit=${limit}` : "";
|
|
2821
|
+
const res = await this.request("GET", `/api/v1/agent/hot_nodes${q}`);
|
|
2822
|
+
const nodes = Array.isArray(res) ? res : res?.hot_nodes ?? res?.nodes ?? [];
|
|
2823
|
+
return { nodes };
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Delete a memory node.
|
|
2827
|
+
*/
|
|
2828
|
+
async delete_memory(id) {
|
|
2829
|
+
return this.request("DELETE", `/api/v1/agent/nodes/${id}`);
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Health probe — check if sidecar is reachable.
|
|
2833
|
+
* Returns true if endpoint responds, false otherwise.
|
|
2834
|
+
*/
|
|
2835
|
+
async probe() {
|
|
2836
|
+
try {
|
|
2837
|
+
await this.request("GET", "/api/v1/agent/hot_nodes?limit=1");
|
|
2838
|
+
return true;
|
|
2839
|
+
} catch {
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Batch heat boost — boost multiple nodes at once.
|
|
2845
|
+
*/
|
|
2846
|
+
async boost_batch(boosts) {
|
|
2847
|
+
try {
|
|
2848
|
+
await this.request("POST", "/api/v1/agent/boost_batch", { boosts });
|
|
2849
|
+
return true;
|
|
2850
|
+
} catch {
|
|
2851
|
+
return false;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
|
|
2856
|
+
// src/retry-queue.ts
|
|
2857
|
+
var RetryQueue = class {
|
|
2858
|
+
items = /* @__PURE__ */ new Map();
|
|
2859
|
+
maxItems;
|
|
2860
|
+
maxRetries;
|
|
2861
|
+
logger;
|
|
2862
|
+
flushing = false;
|
|
2863
|
+
constructor(opts = {}) {
|
|
2864
|
+
this.maxItems = opts.maxItems ?? 500;
|
|
2865
|
+
this.maxRetries = opts.maxRetries ?? 5;
|
|
2866
|
+
this.logger = opts.logger ?? { info: () => {
|
|
2867
|
+
}, warn: () => {
|
|
2868
|
+
}, debug: () => {
|
|
2869
|
+
} };
|
|
2870
|
+
}
|
|
2871
|
+
/** Number of items currently queued. */
|
|
2872
|
+
get size() {
|
|
2873
|
+
return this.items.size;
|
|
2874
|
+
}
|
|
2875
|
+
/** Whether a flush is currently in progress. */
|
|
2876
|
+
get isFlushing() {
|
|
2877
|
+
return this.flushing;
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Enqueue an operation for retry.
|
|
2881
|
+
* If an item with the same key already exists, it's updated (latest payload wins).
|
|
2882
|
+
*/
|
|
2883
|
+
enqueue(key, operation, payload) {
|
|
2884
|
+
const existing = this.items.get(key);
|
|
2885
|
+
if (existing) {
|
|
2886
|
+
existing.payload = payload;
|
|
2887
|
+
existing.operation = operation;
|
|
2888
|
+
this.logger.debug(`sulcus-retry: updated existing item ${key} (attempts: ${existing.attempts})`);
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
if (this.items.size >= this.maxItems) {
|
|
2892
|
+
const oldestKey = this.items.keys().next().value;
|
|
2893
|
+
if (oldestKey !== void 0) {
|
|
2894
|
+
this.items.delete(oldestKey);
|
|
2895
|
+
this.logger.warn(`sulcus-retry: queue full (${this.maxItems}), evicted oldest item ${oldestKey}`);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
this.items.set(key, {
|
|
2899
|
+
key,
|
|
2900
|
+
operation,
|
|
2901
|
+
payload,
|
|
2902
|
+
attempts: 0,
|
|
2903
|
+
enqueuedAt: Date.now(),
|
|
2904
|
+
lastAttemptAt: 0
|
|
2905
|
+
});
|
|
2906
|
+
this.logger.debug(`sulcus-retry: enqueued ${operation} for ${key} (queue size: ${this.items.size})`);
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Flush the queue — attempt all pending retries.
|
|
2910
|
+
*
|
|
2911
|
+
* Calls `executor` for each item. If the executor succeeds, the item is removed.
|
|
2912
|
+
* If it throws, the item's attempt count is incremented; items exceeding
|
|
2913
|
+
* `maxRetries` are dropped.
|
|
2914
|
+
*
|
|
2915
|
+
* Returns the number of successfully flushed items.
|
|
2916
|
+
*/
|
|
2917
|
+
async flush(executor) {
|
|
2918
|
+
if (this.flushing) {
|
|
2919
|
+
this.logger.debug("sulcus-retry: flush already in progress, skipping");
|
|
2920
|
+
return { flushed: 0, failed: 0, dropped: 0 };
|
|
2921
|
+
}
|
|
2922
|
+
if (this.items.size === 0) {
|
|
2923
|
+
return { flushed: 0, failed: 0, dropped: 0 };
|
|
2924
|
+
}
|
|
2925
|
+
this.flushing = true;
|
|
2926
|
+
let flushed = 0;
|
|
2927
|
+
let failed = 0;
|
|
2928
|
+
let dropped = 0;
|
|
2929
|
+
const keys = [...this.items.keys()];
|
|
2930
|
+
for (const key of keys) {
|
|
2931
|
+
const item = this.items.get(key);
|
|
2932
|
+
if (!item) continue;
|
|
2933
|
+
item.attempts++;
|
|
2934
|
+
item.lastAttemptAt = Date.now();
|
|
2935
|
+
try {
|
|
2936
|
+
await executor(item);
|
|
2937
|
+
this.items.delete(key);
|
|
2938
|
+
flushed++;
|
|
2939
|
+
} catch (err) {
|
|
2940
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2941
|
+
item.lastError = msg;
|
|
2942
|
+
if (item.attempts >= this.maxRetries) {
|
|
2943
|
+
this.items.delete(key);
|
|
2944
|
+
dropped++;
|
|
2945
|
+
this.logger.warn(`sulcus-retry: dropped ${item.operation} for ${key} after ${item.attempts} attempts: ${msg}`);
|
|
2946
|
+
} else {
|
|
2947
|
+
failed++;
|
|
2948
|
+
this.logger.debug(`sulcus-retry: ${item.operation} for ${key} failed (attempt ${item.attempts}/${this.maxRetries}): ${msg}`);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
this.flushing = false;
|
|
2953
|
+
if (flushed > 0 || dropped > 0) {
|
|
2954
|
+
this.logger.info(`sulcus-retry: flush complete \u2014 flushed: ${flushed}, failed: ${failed}, dropped: ${dropped}, remaining: ${this.items.size}`);
|
|
2955
|
+
}
|
|
2956
|
+
return { flushed, failed, dropped };
|
|
2957
|
+
}
|
|
2958
|
+
/** Clear all items from the queue. */
|
|
2959
|
+
clear() {
|
|
2960
|
+
this.items.clear();
|
|
2961
|
+
}
|
|
2962
|
+
/** Get a diagnostic snapshot of the queue state. */
|
|
2963
|
+
snapshot() {
|
|
2964
|
+
return {
|
|
2965
|
+
size: this.items.size,
|
|
2966
|
+
flushing: this.flushing,
|
|
2967
|
+
items: [...this.items.values()]
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
|
|
2972
|
+
// src/context-engine.ts
|
|
2973
|
+
var DEFAULT_THRESHOLDS = {
|
|
2974
|
+
compactionTriggerRatio: 0.75,
|
|
2975
|
+
trimTriggerRatio: 0.65,
|
|
2976
|
+
largeResultChars: 3e3,
|
|
2977
|
+
trimHeadChars: 1500,
|
|
2978
|
+
trimTailChars: 1500,
|
|
2979
|
+
emergencyHeadChars: 500,
|
|
2980
|
+
emergencyTailChars: 500,
|
|
2981
|
+
emergencyBrakeRatio: 0.9,
|
|
2982
|
+
minTurnsBetweenCompaction: 3,
|
|
2983
|
+
highGrowthRateThreshold: 1e4,
|
|
2984
|
+
charsPerToken: 4,
|
|
2985
|
+
captureMinChars: 4e3,
|
|
2986
|
+
maxCapturesPerTurn: 3,
|
|
2987
|
+
captureTriggerRatio: 0.55,
|
|
2988
|
+
cumulativeToolCharsThreshold: 5e4,
|
|
2989
|
+
cumulativePressureRatio: 0.5,
|
|
2990
|
+
knowledgeCaptureInterval: 8,
|
|
2991
|
+
knowledgeCaptureRatio: 0.4,
|
|
2992
|
+
constructiveMinRecentTurns: 4,
|
|
2993
|
+
assemblyInjectRatio: 0.85,
|
|
2994
|
+
assemblyRecallRatio: 0.7,
|
|
2995
|
+
assemblyRecallCapRatio: 0.8,
|
|
2996
|
+
constructiveRecentBudgetRatio: 0.6,
|
|
2997
|
+
compressSentenceMaxChars: 200,
|
|
2998
|
+
compressMaxChars: 600,
|
|
2999
|
+
sessionTtlMs: 2 * 60 * 60 * 1e3
|
|
3000
|
+
// 2 hours
|
|
3001
|
+
};
|
|
3002
|
+
var DECISION_MARKERS = ["decided", "will use", "going to", "plan is", "the fix", "conclusion", "recommend", "approach"];
|
|
3003
|
+
var SulcusContextEngine = class {
|
|
3004
|
+
info;
|
|
3005
|
+
logger;
|
|
3006
|
+
delegateCompaction;
|
|
3007
|
+
assemblyMode;
|
|
3008
|
+
compactMode;
|
|
3009
|
+
memoryClient;
|
|
3010
|
+
namespace;
|
|
3011
|
+
/** Merged thresholds (defaults + user overrides). */
|
|
3012
|
+
t;
|
|
3013
|
+
// Compaction tracking per session
|
|
3014
|
+
lastCompactionTurn = /* @__PURE__ */ new Map();
|
|
3015
|
+
turnCounter = /* @__PURE__ */ new Map();
|
|
3016
|
+
// Phase 4: Track message IDs already captured to Sulcus
|
|
3017
|
+
capturedMsgIds = /* @__PURE__ */ new Map();
|
|
3018
|
+
// Phase 5: Track session knowledge capture turns
|
|
3019
|
+
lastKnowledgeCaptureTurn = /* @__PURE__ */ new Map();
|
|
3020
|
+
// Phase 5.5: Growth rate tracking (tokens at previous turn, for delta)
|
|
3021
|
+
lastTokenCount = /* @__PURE__ */ new Map();
|
|
3022
|
+
growthRate = /* @__PURE__ */ new Map();
|
|
3023
|
+
// tokens/turn EMA
|
|
3024
|
+
// Phase 5.5: Cumulative tool result chars per session
|
|
3025
|
+
cumulativeToolChars = /* @__PURE__ */ new Map();
|
|
3026
|
+
// Bug fix: Track which message IDs have been counted for cumulative tool chars
|
|
3027
|
+
countedToolMsgIds = /* @__PURE__ */ new Map();
|
|
3028
|
+
// Phase 6: Working memory cache per session — summaries of tool results
|
|
3029
|
+
workingMemory = /* @__PURE__ */ new Map();
|
|
3030
|
+
// Bug fix: Track last-scanned message index for knowledge capture per session
|
|
3031
|
+
lastKnowledgeScanIndex = /* @__PURE__ */ new Map();
|
|
3032
|
+
// Bug fix: Track last activity timestamp per session for TTL eviction
|
|
3033
|
+
sessionLastActivity = /* @__PURE__ */ new Map();
|
|
3034
|
+
constructor(config) {
|
|
3035
|
+
this.logger = config.logger;
|
|
3036
|
+
this.delegateCompaction = config.delegateCompaction;
|
|
3037
|
+
this.assemblyMode = config.assemblyMode;
|
|
3038
|
+
this.compactMode = config.compactMode ?? "smart";
|
|
3039
|
+
this.memoryClient = config.memoryClient ?? null;
|
|
3040
|
+
this.namespace = config.namespace ?? "default";
|
|
3041
|
+
this.t = { ...DEFAULT_THRESHOLDS, ...config.thresholds };
|
|
3042
|
+
this.info = {
|
|
3043
|
+
id: "openclaw-sulcus",
|
|
3044
|
+
name: "Sulcus Context Engine",
|
|
3045
|
+
version: config.version,
|
|
3046
|
+
ownsCompaction: true,
|
|
3047
|
+
turnMaintenanceMode: "foreground"
|
|
3048
|
+
};
|
|
3049
|
+
this.logger.info(
|
|
3050
|
+
`sulcus-context-engine: initialized v${config.version} (assembly=${this.assemblyMode}, compact=${this.compactMode}, capture=unified, overflow=hardened)`
|
|
3051
|
+
);
|
|
3052
|
+
this.logger.info(
|
|
3053
|
+
`sulcus-context-engine: thresholds: ${JSON.stringify(this.t)}`
|
|
3054
|
+
);
|
|
3055
|
+
}
|
|
3056
|
+
// ---------------------------------------------------------------------------
|
|
3057
|
+
// Bootstrap / Maintain / Ingest — still no-op
|
|
3058
|
+
// ---------------------------------------------------------------------------
|
|
3059
|
+
async bootstrap(_params) {
|
|
3060
|
+
return { bootstrapped: false, reason: "phase-5" };
|
|
3061
|
+
}
|
|
3062
|
+
async maintain(_params) {
|
|
3063
|
+
return { changed: false, bytesFreed: 0, rewrittenEntries: 0, reason: "phase-5" };
|
|
3064
|
+
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Ingest a single message into working memory.
|
|
3067
|
+
* For tool results: cache metadata, store full content to Sulcus (SILU generates pointer_summary).
|
|
3068
|
+
* Uses unified capturedMsgIds dedup guard to prevent triple-ingestion.
|
|
3069
|
+
*/
|
|
3070
|
+
async ingest(params) {
|
|
3071
|
+
const { sessionId, message } = params;
|
|
3072
|
+
if (!message || message.role !== "tool" || typeof message.content !== "string") {
|
|
3073
|
+
return { ingested: false };
|
|
3074
|
+
}
|
|
3075
|
+
if (!message.id || message.content.length < 200) {
|
|
3076
|
+
return { ingested: false };
|
|
3077
|
+
}
|
|
3078
|
+
const sessionCache = this.getSessionCache(sessionId);
|
|
3079
|
+
if (sessionCache.has(message.id)) {
|
|
3080
|
+
return { ingested: false };
|
|
3081
|
+
}
|
|
3082
|
+
const sessionCaptured = this.getSessionCapturedIds(sessionId);
|
|
3083
|
+
const toolName = message.name || "tool";
|
|
3084
|
+
const turn = this.turnCounter.get(sessionId) ?? 0;
|
|
3085
|
+
const entry = {
|
|
3086
|
+
messageId: message.id,
|
|
3087
|
+
toolName,
|
|
3088
|
+
originalLength: message.content.length,
|
|
3089
|
+
turn
|
|
3090
|
+
};
|
|
3091
|
+
if (this.memoryClient && message.content.length >= this.t.captureMinChars && !sessionCaptured.has(message.id)) {
|
|
3092
|
+
try {
|
|
3093
|
+
const res = await this.memoryClient.add_memory(
|
|
3094
|
+
`[Tool: ${toolName}]
|
|
3095
|
+
${message.content}`,
|
|
3096
|
+
"episodic",
|
|
3097
|
+
{ key_points: [`tool-result:${toolName}`, `session:${sessionId}`, `msg:${message.id}`] }
|
|
3098
|
+
);
|
|
3099
|
+
entry.sulcusNodeId = res?.id;
|
|
3100
|
+
sessionCaptured.add(message.id);
|
|
3101
|
+
this.logger.debug(`sulcus-ce: ingested tool result to memory (${toolName}, ${message.content.length} chars) [msg=${message.id}]`);
|
|
3102
|
+
} catch {
|
|
3103
|
+
this.logger.debug(`sulcus-ce: memory store failed for ingest [msg=${message.id}]`);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
sessionCache.set(message.id, entry);
|
|
3107
|
+
return { ingested: true };
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Batch ingest messages into working memory.
|
|
3111
|
+
*/
|
|
3112
|
+
async ingestBatch(params) {
|
|
3113
|
+
const { sessionId, messages } = params;
|
|
3114
|
+
if (!messages || !Array.isArray(messages)) return { ingestedCount: 0 };
|
|
3115
|
+
let count = 0;
|
|
3116
|
+
for (const msg of messages) {
|
|
3117
|
+
const res = await this.ingest({ sessionId, message: msg });
|
|
3118
|
+
if (res.ingested) count++;
|
|
3119
|
+
}
|
|
3120
|
+
return { ingestedCount: count };
|
|
3121
|
+
}
|
|
3122
|
+
/** Get or create the working memory cache for a session. */
|
|
3123
|
+
getSessionCache(sessionId) {
|
|
3124
|
+
let cache = this.workingMemory.get(sessionId);
|
|
3125
|
+
if (!cache) {
|
|
3126
|
+
cache = /* @__PURE__ */ new Map();
|
|
3127
|
+
this.workingMemory.set(sessionId, cache);
|
|
3128
|
+
}
|
|
3129
|
+
return cache;
|
|
3130
|
+
}
|
|
3131
|
+
/** Get or create the captured message ID set for a session (unified dedup guard). */
|
|
3132
|
+
getSessionCapturedIds(sessionId) {
|
|
3133
|
+
let ids = this.capturedMsgIds.get(sessionId);
|
|
3134
|
+
if (!ids) {
|
|
3135
|
+
ids = /* @__PURE__ */ new Set();
|
|
3136
|
+
this.capturedMsgIds.set(sessionId, ids);
|
|
3137
|
+
}
|
|
3138
|
+
return ids;
|
|
3139
|
+
}
|
|
3140
|
+
/** Get or create the counted tool message ID set for a session. */
|
|
3141
|
+
getSessionCountedIds(sessionId) {
|
|
3142
|
+
let ids = this.countedToolMsgIds.get(sessionId);
|
|
3143
|
+
if (!ids) {
|
|
3144
|
+
ids = /* @__PURE__ */ new Set();
|
|
3145
|
+
this.countedToolMsgIds.set(sessionId, ids);
|
|
3146
|
+
}
|
|
3147
|
+
return ids;
|
|
3148
|
+
}
|
|
3149
|
+
// ---------------------------------------------------------------------------
|
|
3150
|
+
// Session lifecycle — cleanup + TTL eviction
|
|
3151
|
+
// ---------------------------------------------------------------------------
|
|
3152
|
+
/**
|
|
3153
|
+
* Clear all per-session state for a given session.
|
|
3154
|
+
* Call when a session ends (e.g. from onSubagentEnded or dispose).
|
|
3155
|
+
*/
|
|
3156
|
+
clearSession(sessionId) {
|
|
3157
|
+
this.turnCounter.delete(sessionId);
|
|
3158
|
+
this.lastCompactionTurn.delete(sessionId);
|
|
3159
|
+
this.capturedMsgIds.delete(sessionId);
|
|
3160
|
+
this.countedToolMsgIds.delete(sessionId);
|
|
3161
|
+
this.lastKnowledgeCaptureTurn.delete(sessionId);
|
|
3162
|
+
this.lastKnowledgeScanIndex.delete(sessionId);
|
|
3163
|
+
this.lastTokenCount.delete(sessionId);
|
|
3164
|
+
this.growthRate.delete(sessionId);
|
|
3165
|
+
this.cumulativeToolChars.delete(sessionId);
|
|
3166
|
+
this.workingMemory.delete(sessionId);
|
|
3167
|
+
this.sessionLastActivity.delete(sessionId);
|
|
3168
|
+
this.logger.debug(`sulcus-ce: cleared session state [session=${sessionId}]`);
|
|
3169
|
+
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Evict stale sessions that haven't been active within the TTL window.
|
|
3172
|
+
* Called periodically from afterTurn to prevent unbounded memory growth.
|
|
3173
|
+
*/
|
|
3174
|
+
evictStaleSessions() {
|
|
3175
|
+
const now = Date.now();
|
|
3176
|
+
const ttl = this.t.sessionTtlMs;
|
|
3177
|
+
const stale = [];
|
|
3178
|
+
for (const [sessionId, lastActivity] of this.sessionLastActivity) {
|
|
3179
|
+
if (now - lastActivity > ttl) {
|
|
3180
|
+
stale.push(sessionId);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
for (const sessionId of stale) {
|
|
3184
|
+
this.clearSession(sessionId);
|
|
3185
|
+
}
|
|
3186
|
+
if (stale.length > 0) {
|
|
3187
|
+
this.logger.info(`sulcus-ce: evicted ${stale.length} stale session(s) (TTL=${ttl}ms)`);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
// ---------------------------------------------------------------------------
|
|
3191
|
+
// afterTurn — THE OVERFLOW PREVENTION + KNOWLEDGE CAPTURE
|
|
3192
|
+
// ---------------------------------------------------------------------------
|
|
3193
|
+
async afterTurn(params) {
|
|
3194
|
+
const {
|
|
3195
|
+
sessionId,
|
|
3196
|
+
sessionFile,
|
|
3197
|
+
messages,
|
|
3198
|
+
tokenBudget,
|
|
3199
|
+
runtimeContext
|
|
3200
|
+
} = params;
|
|
3201
|
+
const turn = (this.turnCounter.get(sessionId) ?? 0) + 1;
|
|
3202
|
+
this.turnCounter.set(sessionId, turn);
|
|
3203
|
+
this.sessionLastActivity.set(sessionId, Date.now());
|
|
3204
|
+
if (turn % 10 === 0) {
|
|
3205
|
+
this.evictStaleSessions();
|
|
3206
|
+
}
|
|
3207
|
+
const budget = tokenBudget ?? runtimeContext?.tokenBudget;
|
|
3208
|
+
const currentTokens = runtimeContext?.currentTokenCount;
|
|
3209
|
+
if (!budget || !currentTokens) return;
|
|
3210
|
+
const usage = currentTokens / budget;
|
|
3211
|
+
const usagePct = (usage * 100).toFixed(1);
|
|
3212
|
+
const prevTokens = this.lastTokenCount.get(sessionId);
|
|
3213
|
+
const hasPrevious = prevTokens !== void 0;
|
|
3214
|
+
this.lastTokenCount.set(sessionId, currentTokens);
|
|
3215
|
+
let newGrowth;
|
|
3216
|
+
if (!hasPrevious) {
|
|
3217
|
+
newGrowth = 0;
|
|
3218
|
+
} else {
|
|
3219
|
+
const tokensAddedThisTurn = Math.max(0, currentTokens - prevTokens);
|
|
3220
|
+
const prevGrowth = this.growthRate.get(sessionId) ?? 0;
|
|
3221
|
+
newGrowth = Math.round(0.3 * tokensAddedThisTurn + 0.7 * prevGrowth);
|
|
3222
|
+
}
|
|
3223
|
+
this.growthRate.set(sessionId, newGrowth);
|
|
3224
|
+
let sessionToolChars = this.cumulativeToolChars.get(sessionId) ?? 0;
|
|
3225
|
+
const countedIds = this.getSessionCountedIds(sessionId);
|
|
3226
|
+
for (const msg of messages) {
|
|
3227
|
+
if (msg.role === "tool" && typeof msg.content === "string" && msg.id) {
|
|
3228
|
+
if (countedIds.has(msg.id)) continue;
|
|
3229
|
+
if (!msg.content.includes("[\u2026 trimmed by sulcus-ce") && !msg.content.includes("[captured by sulcus-ce")) {
|
|
3230
|
+
sessionToolChars += msg.content.length;
|
|
3231
|
+
countedIds.add(msg.id);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
this.cumulativeToolChars.set(sessionId, sessionToolChars);
|
|
3236
|
+
if (usage > 0.5) {
|
|
3237
|
+
this.logger.debug(
|
|
3238
|
+
`sulcus-ce: context pressure ${usagePct}% (${currentTokens}/${budget} tokens, growth: ${newGrowth} tok/turn, cumToolChars: ${sessionToolChars}) [session=${sessionId}, turn=${turn}]`
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
if (usage >= this.t.captureTriggerRatio && this.memoryClient) {
|
|
3242
|
+
await this.captureToolResults(messages, sessionId, turn);
|
|
3243
|
+
}
|
|
3244
|
+
if (usage >= this.t.knowledgeCaptureRatio && this.memoryClient && turn - (this.lastKnowledgeCaptureTurn.get(sessionId) ?? 0) >= this.t.knowledgeCaptureInterval) {
|
|
3245
|
+
await this.captureSessionKnowledge(messages, sessionId, turn, usagePct);
|
|
3246
|
+
}
|
|
3247
|
+
if (this.assemblyMode === "constructive") {
|
|
3248
|
+
const sessionCache = this.getSessionCache(sessionId);
|
|
3249
|
+
const sessionCapturedCtv = this.getSessionCapturedIds(sessionId);
|
|
3250
|
+
let newCached = 0;
|
|
3251
|
+
for (const msg of messages) {
|
|
3252
|
+
if (msg.role !== "tool" || typeof msg.content !== "string" || !msg.id) continue;
|
|
3253
|
+
if (sessionCache.has(msg.id)) continue;
|
|
3254
|
+
if (msg.content.length < 200) continue;
|
|
3255
|
+
const toolName = msg.name || "tool";
|
|
3256
|
+
const entry = {
|
|
3257
|
+
messageId: msg.id,
|
|
3258
|
+
toolName,
|
|
3259
|
+
originalLength: msg.content.length,
|
|
3260
|
+
turn
|
|
3261
|
+
};
|
|
3262
|
+
if (this.memoryClient && msg.content.length >= this.t.captureMinChars && !sessionCapturedCtv.has(msg.id)) {
|
|
3263
|
+
this.memoryClient.add_memory(
|
|
3264
|
+
`[Tool: ${toolName}]
|
|
3265
|
+
${msg.content}`,
|
|
3266
|
+
"episodic",
|
|
3267
|
+
{ key_points: [`tool-result:${toolName}`, `session:${sessionId}`, `msg:${msg.id}`] }
|
|
3268
|
+
).then((res) => {
|
|
3269
|
+
entry.sulcusNodeId = res?.id;
|
|
3270
|
+
sessionCapturedCtv.add(msg.id);
|
|
3271
|
+
}).catch(() => {
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
sessionCache.set(msg.id, entry);
|
|
3275
|
+
newCached++;
|
|
3276
|
+
}
|
|
3277
|
+
if (newCached > 0) {
|
|
3278
|
+
this.logger.debug(`sulcus-ce: cached ${newCached} tool results (total: ${sessionCache.size}) [turn=${turn}]`);
|
|
3279
|
+
}
|
|
3280
|
+
if (usage >= this.t.compactionTriggerRatio) {
|
|
3281
|
+
const lastCompaction = this.lastCompactionTurn.get(sessionId) ?? 0;
|
|
3282
|
+
const turnsSinceCompaction = turn - lastCompaction;
|
|
3283
|
+
const minTurns = newGrowth >= this.t.highGrowthRateThreshold ? 1 : this.t.minTurnsBetweenCompaction;
|
|
3284
|
+
if (turnsSinceCompaction >= minTurns) {
|
|
3285
|
+
this.logger.info(
|
|
3286
|
+
`sulcus-ce: CONSTRUCTIVE COMPACTION at ${usagePct}% (${currentTokens}/${budget}). Growth: ${newGrowth} tok/turn [session=${sessionId}]`
|
|
3287
|
+
);
|
|
3288
|
+
try {
|
|
3289
|
+
const result = await this.delegateCompaction({
|
|
3290
|
+
sessionId,
|
|
3291
|
+
sessionFile: params.sessionFile,
|
|
3292
|
+
tokenBudget: budget,
|
|
3293
|
+
currentTokenCount: currentTokens,
|
|
3294
|
+
force: false,
|
|
3295
|
+
runtimeContext
|
|
3296
|
+
});
|
|
3297
|
+
this.lastCompactionTurn.set(sessionId, turn);
|
|
3298
|
+
if (result.compacted) {
|
|
3299
|
+
const saved = (result.result?.tokensBefore ?? 0) - (result.result?.tokensAfter ?? 0);
|
|
3300
|
+
this.logger.info(`sulcus-ce: constructive compaction succeeded \u2014 saved ~${saved} tokens`);
|
|
3301
|
+
}
|
|
3302
|
+
} catch (e) {
|
|
3303
|
+
this.logger.warn(`sulcus-ce: constructive compaction failed: ${e}`);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
if (usage >= this.t.emergencyBrakeRatio && runtimeContext?.rewriteTranscriptEntries) {
|
|
3310
|
+
this.logger.warn(
|
|
3311
|
+
`sulcus-ce: \u26A0\uFE0F EMERGENCY BRAKE at ${usagePct}% (${currentTokens}/${budget}) \u2014 aggressively trimming ALL tool results [session=${sessionId}, turn=${turn}]`
|
|
3312
|
+
);
|
|
3313
|
+
await this.emergencyTrimAllToolResults(messages, runtimeContext.rewriteTranscriptEntries, sessionId);
|
|
3314
|
+
}
|
|
3315
|
+
if (usage >= this.t.cumulativePressureRatio && sessionToolChars >= this.t.cumulativeToolCharsThreshold && runtimeContext?.rewriteTranscriptEntries && usage < this.t.emergencyBrakeRatio) {
|
|
3316
|
+
this.logger.info(
|
|
3317
|
+
`sulcus-ce: cumulative pressure trim \u2014 ${sessionToolChars} total tool chars, ${usagePct}% budget [session=${sessionId}]`
|
|
3318
|
+
);
|
|
3319
|
+
await this.trimCumulativePressure(messages, runtimeContext.rewriteTranscriptEntries, sessionId);
|
|
3320
|
+
}
|
|
3321
|
+
if (usage >= this.t.trimTriggerRatio && usage < this.t.emergencyBrakeRatio && // emergency already handled
|
|
3322
|
+
runtimeContext?.rewriteTranscriptEntries) {
|
|
3323
|
+
await this.trimLargeToolResults(messages, runtimeContext.rewriteTranscriptEntries, sessionId);
|
|
3324
|
+
}
|
|
3325
|
+
if (usage >= this.t.compactionTriggerRatio) {
|
|
3326
|
+
const lastCompaction = this.lastCompactionTurn.get(sessionId) ?? 0;
|
|
3327
|
+
const turnsSinceCompaction = turn - lastCompaction;
|
|
3328
|
+
const minTurns = newGrowth >= this.t.highGrowthRateThreshold ? 1 : this.t.minTurnsBetweenCompaction;
|
|
3329
|
+
if (turnsSinceCompaction >= minTurns) {
|
|
3330
|
+
this.logger.info(
|
|
3331
|
+
`sulcus-ce: PREEMPTIVE COMPACTION at ${usagePct}% (${currentTokens}/${budget}). Growth: ${newGrowth} tok/turn, interval: ${minTurns}, turns since last: ${turnsSinceCompaction}. [session=${sessionId}]`
|
|
3332
|
+
);
|
|
3333
|
+
try {
|
|
3334
|
+
const result = await this.delegateCompaction({
|
|
3335
|
+
sessionId,
|
|
3336
|
+
sessionFile,
|
|
3337
|
+
tokenBudget: budget,
|
|
3338
|
+
currentTokenCount: currentTokens,
|
|
3339
|
+
force: false,
|
|
3340
|
+
runtimeContext
|
|
3341
|
+
});
|
|
3342
|
+
this.lastCompactionTurn.set(sessionId, turn);
|
|
3343
|
+
if (result.compacted) {
|
|
3344
|
+
const saved = (result.result?.tokensBefore ?? 0) - (result.result?.tokensAfter ?? 0);
|
|
3345
|
+
this.logger.info(`sulcus-ce: compaction succeeded \u2014 saved ~${saved} tokens`);
|
|
3346
|
+
} else {
|
|
3347
|
+
this.logger.debug(`sulcus-ce: compaction declined: ${result.reason ?? "unknown"}`);
|
|
3348
|
+
}
|
|
3349
|
+
} catch (e) {
|
|
3350
|
+
this.logger.warn(`sulcus-ce: preemptive compaction failed: ${e}`);
|
|
3351
|
+
}
|
|
3352
|
+
} else {
|
|
3353
|
+
this.logger.debug(
|
|
3354
|
+
`sulcus-ce: skipping compaction (only ${turnsSinceCompaction} turns since last, need ${minTurns})`
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
// ---------------------------------------------------------------------------
|
|
3360
|
+
// Phase 4: Capture tool results to Sulcus before trimming
|
|
3361
|
+
// ---------------------------------------------------------------------------
|
|
3362
|
+
async captureToolResults(messages, sessionId, turn) {
|
|
3363
|
+
const sessionCaptured = this.getSessionCapturedIds(sessionId);
|
|
3364
|
+
let captureCount = 0;
|
|
3365
|
+
for (const msg of messages) {
|
|
3366
|
+
if (captureCount >= this.t.maxCapturesPerTurn) break;
|
|
3367
|
+
if (msg.role !== "tool" || typeof msg.content !== "string") continue;
|
|
3368
|
+
if (!msg.id || msg.content.length < this.t.captureMinChars) continue;
|
|
3369
|
+
if (sessionCaptured.has(msg.id)) continue;
|
|
3370
|
+
if (msg.content.includes("[\u2026 trimmed by sulcus-ce")) continue;
|
|
3371
|
+
if (msg.content.includes("[captured by sulcus-ce")) continue;
|
|
3372
|
+
try {
|
|
3373
|
+
const toolName = msg.name || "tool-result";
|
|
3374
|
+
const firstLine = msg.content.slice(0, 200).split("\n")[0];
|
|
3375
|
+
const captureContent = [
|
|
3376
|
+
`[Tool result: ${toolName}] ${firstLine}`,
|
|
3377
|
+
"",
|
|
3378
|
+
msg.content
|
|
3379
|
+
].join("\n");
|
|
3380
|
+
await this.memoryClient.add_memory(captureContent, "episodic", {
|
|
3381
|
+
key_points: [`tool-result:${toolName}`, `session:${sessionId}`, `msg:${msg.id}`]
|
|
3382
|
+
});
|
|
3383
|
+
sessionCaptured.add(msg.id);
|
|
3384
|
+
captureCount++;
|
|
3385
|
+
this.logger.debug(
|
|
3386
|
+
`sulcus-ce: captured tool result to memory (${toolName}, ${msg.content.length} chars) [msg=${msg.id}]`
|
|
3387
|
+
);
|
|
3388
|
+
} catch (e) {
|
|
3389
|
+
this.logger.warn(`sulcus-ce: memory capture failed for msg ${msg.id}: ${e}`);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
if (captureCount > 0) {
|
|
3393
|
+
this.logger.info(`sulcus-ce: captured ${captureCount} tool results to Sulcus memory [session=${sessionId}, turn=${turn}]`);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
// ---------------------------------------------------------------------------
|
|
3397
|
+
// Phase 5: Continuous session knowledge capture
|
|
3398
|
+
// ---------------------------------------------------------------------------
|
|
3399
|
+
async captureSessionKnowledge(messages, sessionId, turn, usagePct) {
|
|
3400
|
+
this.lastKnowledgeCaptureTurn.set(sessionId, turn);
|
|
3401
|
+
try {
|
|
3402
|
+
const lastScanIdx = this.lastKnowledgeScanIndex.get(sessionId) ?? 0;
|
|
3403
|
+
const messagesToScan = messages.slice(lastScanIdx);
|
|
3404
|
+
this.lastKnowledgeScanIndex.set(sessionId, messages.length);
|
|
3405
|
+
const decisions = [];
|
|
3406
|
+
const filesModified = [];
|
|
3407
|
+
const commandsRun = [];
|
|
3408
|
+
const userIntents = [];
|
|
3409
|
+
for (const msg of messagesToScan) {
|
|
3410
|
+
const role = msg.role;
|
|
3411
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
3412
|
+
if (role === "user" && content.length > 10) {
|
|
3413
|
+
userIntents.push(content.substring(0, 150));
|
|
3414
|
+
}
|
|
3415
|
+
if (role === "assistant" && content.length > 20) {
|
|
3416
|
+
const lc = content.toLowerCase();
|
|
3417
|
+
if (DECISION_MARKERS.some((m) => lc.includes(m))) {
|
|
3418
|
+
const sentences = content.split(/[.!?\n]/).filter((s) => s.trim().length > 10);
|
|
3419
|
+
for (const s of sentences) {
|
|
3420
|
+
if (DECISION_MARKERS.some((m) => s.toLowerCase().includes(m)) && !decisions.includes(s.trim())) {
|
|
3421
|
+
decisions.push(s.trim().substring(0, 200));
|
|
3422
|
+
if (decisions.length >= 5) break;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
|
|
3428
|
+
for (const tc of toolCalls) {
|
|
3429
|
+
const name = tc.name ?? tc.function;
|
|
3430
|
+
if (name === "Write" || name === "Edit" || name === "write" || name === "edit") {
|
|
3431
|
+
const input = tc.input ?? tc.arguments ?? {};
|
|
3432
|
+
const fp = input?.file_path ?? input?.path;
|
|
3433
|
+
if (fp && typeof fp === "string" && !filesModified.includes(fp)) filesModified.push(fp);
|
|
3434
|
+
}
|
|
3435
|
+
if (name === "Bash" || name === "bash" || name === "exec" || name === "shell") {
|
|
3436
|
+
const input = tc.input ?? tc.arguments ?? {};
|
|
3437
|
+
const cmd = input?.command ?? input?.cmd;
|
|
3438
|
+
if (cmd && typeof cmd === "string" && commandsRun.length < 5) {
|
|
3439
|
+
commandsRun.push(cmd.substring(0, 100));
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
const storePromises = [];
|
|
3445
|
+
if (decisions.length > 0) {
|
|
3446
|
+
const decisionText = `Session decisions (turn ${turn}): ${decisions.join(" | ")}`;
|
|
3447
|
+
storePromises.push(
|
|
3448
|
+
this.memoryClient.add_memory(decisionText, "semantic", {
|
|
3449
|
+
key_points: [`session:${sessionId}`, "decisions", `turn:${turn}`]
|
|
3450
|
+
}).catch((e) => this.logger.debug(`sulcus-ce: decision capture failed: ${e}`))
|
|
3451
|
+
);
|
|
3452
|
+
}
|
|
3453
|
+
if (this.memoryClient.store_episode) {
|
|
3454
|
+
const firstUser = messages.find((m) => m.role === "user" && typeof m.content === "string");
|
|
3455
|
+
const episode = {
|
|
3456
|
+
topic: typeof firstUser?.content === "string" ? firstUser.content.substring(0, 200) : "(none)",
|
|
3457
|
+
decisions: decisions.slice(0, 5),
|
|
3458
|
+
files_modified: filesModified.slice(0, 10),
|
|
3459
|
+
commands_run: commandsRun.slice(0, 5),
|
|
3460
|
+
outcome: "in-progress",
|
|
3461
|
+
duration_turns: messages.length,
|
|
3462
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3463
|
+
};
|
|
3464
|
+
storePromises.push(
|
|
3465
|
+
this.memoryClient.store_episode(episode).catch((e) => this.logger.debug(`sulcus-ce: episode capture failed: ${e}`))
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
await Promise.allSettled(storePromises);
|
|
3469
|
+
if (storePromises.length > 0) {
|
|
3470
|
+
this.logger.info(`sulcus-ce: session knowledge capture \u2014 stored ${storePromises.length} memories (turn ${turn}, ${usagePct}% budget)`);
|
|
3471
|
+
}
|
|
3472
|
+
} catch (e) {
|
|
3473
|
+
this.logger.warn(`sulcus-ce: session knowledge capture failed: ${e}`);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
// ---------------------------------------------------------------------------
|
|
3477
|
+
// Trim large tool results (capture-aware)
|
|
3478
|
+
// ---------------------------------------------------------------------------
|
|
3479
|
+
async trimLargeToolResults(messages, rewriteTranscript, sessionId) {
|
|
3480
|
+
const sessionCaptured = this.getSessionCapturedIds(sessionId);
|
|
3481
|
+
const replacements = [];
|
|
3482
|
+
for (const msg of messages) {
|
|
3483
|
+
if (msg.role !== "tool" || typeof msg.content !== "string") continue;
|
|
3484
|
+
if (msg.content.length <= this.t.largeResultChars) continue;
|
|
3485
|
+
if (!msg.id) continue;
|
|
3486
|
+
if (msg.content.includes("[\u2026 trimmed by sulcus-ce")) continue;
|
|
3487
|
+
if (msg.content.includes("[captured by sulcus-ce")) continue;
|
|
3488
|
+
const wasCaptured = sessionCaptured.has(msg.id);
|
|
3489
|
+
const toolName = msg.name || "tool-result";
|
|
3490
|
+
const head = msg.content.slice(0, this.t.trimHeadChars);
|
|
3491
|
+
const tail = msg.content.slice(-this.t.trimTailChars);
|
|
3492
|
+
const trimmed = msg.content.length - this.t.trimHeadChars - this.t.trimTailChars;
|
|
3493
|
+
const marker = wasCaptured ? `[captured by sulcus-ce \u2014 full content stored in memory, use memory_recall for "${toolName}" to retrieve]` : `[\u2026 trimmed by sulcus-ce: ${trimmed} chars removed \u2026]`;
|
|
3494
|
+
replacements.push({
|
|
3495
|
+
entryId: msg.id,
|
|
3496
|
+
message: { ...msg, content: `${head}
|
|
3497
|
+
|
|
3498
|
+
${marker}
|
|
3499
|
+
|
|
3500
|
+
${tail}` }
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
3503
|
+
if (replacements.length > 0) {
|
|
3504
|
+
try {
|
|
3505
|
+
const result = await rewriteTranscript({ replacements });
|
|
3506
|
+
if (result.changed) {
|
|
3507
|
+
this.logger.info(
|
|
3508
|
+
`sulcus-ce: trimmed ${result.rewrittenEntries} large tool results, freed ~${result.bytesFreed} bytes [session=${sessionId}]`
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
} catch (e) {
|
|
3512
|
+
this.logger.warn(`sulcus-ce: transcript rewrite failed: ${e}`);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
// ---------------------------------------------------------------------------
|
|
3517
|
+
// Emergency brake: aggressively trim ALL tool results at 90%+ budget.
|
|
3518
|
+
// Last-resort guard before context overflow. 500 char head + 500 char tail.
|
|
3519
|
+
// ---------------------------------------------------------------------------
|
|
3520
|
+
async emergencyTrimAllToolResults(messages, rewriteTranscript, sessionId) {
|
|
3521
|
+
const replacements = [];
|
|
3522
|
+
for (const msg of messages) {
|
|
3523
|
+
if (msg.role !== "tool" || typeof msg.content !== "string") continue;
|
|
3524
|
+
if (!msg.id) continue;
|
|
3525
|
+
if (msg.content.length <= this.t.emergencyHeadChars + this.t.emergencyTailChars + 200) continue;
|
|
3526
|
+
if (msg.content.includes("[\u26A0\uFE0F EMERGENCY trimmed by sulcus-ce")) continue;
|
|
3527
|
+
const head = msg.content.slice(0, this.t.emergencyHeadChars);
|
|
3528
|
+
const tail = msg.content.slice(-this.t.emergencyTailChars);
|
|
3529
|
+
const trimmed = msg.content.length - this.t.emergencyHeadChars - this.t.emergencyTailChars;
|
|
3530
|
+
replacements.push({
|
|
3531
|
+
entryId: msg.id,
|
|
3532
|
+
message: {
|
|
3533
|
+
...msg,
|
|
3534
|
+
content: `${head}
|
|
3535
|
+
|
|
3536
|
+
[\u26A0\uFE0F EMERGENCY trimmed by sulcus-ce: ${trimmed} chars removed \u2014 context at 90%+ budget]
|
|
3537
|
+
|
|
3538
|
+
${tail}`
|
|
3539
|
+
}
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
if (replacements.length > 0) {
|
|
3543
|
+
try {
|
|
3544
|
+
const result = await rewriteTranscript({ replacements });
|
|
3545
|
+
if (result.changed) {
|
|
3546
|
+
this.logger.warn(
|
|
3547
|
+
`sulcus-ce: \u26A0\uFE0F EMERGENCY trimmed ${result.rewrittenEntries} tool results, freed ~${result.bytesFreed} bytes [session=${sessionId}]`
|
|
3548
|
+
);
|
|
3549
|
+
}
|
|
3550
|
+
} catch (e) {
|
|
3551
|
+
this.logger.warn(`sulcus-ce: emergency transcript rewrite failed: ${e}`);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
// ---------------------------------------------------------------------------
|
|
3556
|
+
// Cumulative pressure trimming: when total tool output exceeds 50k chars AND
|
|
3557
|
+
// budget usage is >50%, trim the oldest/largest tool results even if they’re
|
|
3558
|
+
// individually under LARGE_RESULT_CHARS. Targets the biggest offenders first.
|
|
3559
|
+
// ---------------------------------------------------------------------------
|
|
3560
|
+
async trimCumulativePressure(messages, rewriteTranscript, sessionId) {
|
|
3561
|
+
const toolMsgs = [];
|
|
3562
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3563
|
+
const msg = messages[i];
|
|
3564
|
+
if (msg.role !== "tool" || typeof msg.content !== "string" || !msg.id) continue;
|
|
3565
|
+
if (msg.content.includes("[\u2026 trimmed by sulcus-ce")) continue;
|
|
3566
|
+
if (msg.content.includes("[captured by sulcus-ce")) continue;
|
|
3567
|
+
if (msg.content.includes("[\u26A0\uFE0F EMERGENCY trimmed")) continue;
|
|
3568
|
+
if (msg.content.length < 1e3) continue;
|
|
3569
|
+
toolMsgs.push({ idx: i, msg, size: msg.content.length });
|
|
3570
|
+
}
|
|
3571
|
+
if (toolMsgs.length === 0) return;
|
|
3572
|
+
toolMsgs.sort((a, b) => b.size - a.size);
|
|
3573
|
+
const trimCount = Math.max(1, Math.ceil(toolMsgs.length / 2));
|
|
3574
|
+
const sessionCaptured = this.getSessionCapturedIds(sessionId);
|
|
3575
|
+
const replacements = [];
|
|
3576
|
+
for (let i = 0; i < trimCount; i++) {
|
|
3577
|
+
const { msg } = toolMsgs[i];
|
|
3578
|
+
const wasCaptured = sessionCaptured.has(msg.id);
|
|
3579
|
+
const toolName = msg.name || "tool-result";
|
|
3580
|
+
const head = msg.content.slice(0, this.t.trimHeadChars);
|
|
3581
|
+
const tail = msg.content.slice(-this.t.trimTailChars);
|
|
3582
|
+
const trimmed = msg.content.length - this.t.trimHeadChars - this.t.trimTailChars;
|
|
3583
|
+
if (trimmed <= 0) continue;
|
|
3584
|
+
const marker = wasCaptured ? `[captured by sulcus-ce \u2014 full content stored in memory, use memory_recall for "${toolName}" to retrieve]` : `[\u2026 trimmed by sulcus-ce (cumulative pressure): ${trimmed} chars removed \u2026]`;
|
|
3585
|
+
replacements.push({
|
|
3586
|
+
entryId: msg.id,
|
|
3587
|
+
message: { ...msg, content: `${head}
|
|
3588
|
+
|
|
3589
|
+
${marker}
|
|
3590
|
+
|
|
3591
|
+
${tail}` }
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
if (replacements.length > 0) {
|
|
3595
|
+
try {
|
|
3596
|
+
const result = await rewriteTranscript({ replacements });
|
|
3597
|
+
if (result.changed) {
|
|
3598
|
+
this.logger.info(
|
|
3599
|
+
`sulcus-ce: cumulative pressure trimmed ${result.rewrittenEntries} tool results, freed ~${result.bytesFreed} bytes [session=${sessionId}]`
|
|
3600
|
+
);
|
|
3601
|
+
}
|
|
3602
|
+
} catch (e) {
|
|
3603
|
+
this.logger.warn(`sulcus-ce: cumulative pressure rewrite failed: ${e}`);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
// ---------------------------------------------------------------------------
|
|
3608
|
+
// Assemble — Phase 5: Memory-Aware Assembly with Full Recall
|
|
3609
|
+
// ---------------------------------------------------------------------------
|
|
3610
|
+
async assemble(params) {
|
|
3611
|
+
if (this.assemblyMode === "constructive") {
|
|
3612
|
+
return this.assembleConstructive(params);
|
|
3613
|
+
}
|
|
3614
|
+
if (this.assemblyMode !== "memory-aware" || !this.memoryClient) {
|
|
3615
|
+
return { messages: params.messages, estimatedTokens: 0 };
|
|
3616
|
+
}
|
|
3617
|
+
const { messages, tokenBudget, sessionId } = params;
|
|
3618
|
+
try {
|
|
3619
|
+
const recentUserMsgs = messages.filter((m) => m.role === "user" && typeof m.content === "string").slice(-3);
|
|
3620
|
+
if (recentUserMsgs.length === 0) {
|
|
3621
|
+
return { messages, estimatedTokens: 0 };
|
|
3622
|
+
}
|
|
3623
|
+
const topicText = recentUserMsgs.map((m) => m.content).join(" ").slice(0, 500);
|
|
3624
|
+
const searchRes = await this.memoryClient.search_memory(topicText, 8, this.namespace);
|
|
3625
|
+
const memories = searchRes?.results ?? [];
|
|
3626
|
+
if (memories.length === 0) {
|
|
3627
|
+
this.logger.debug(`sulcus-ce: assemble \u2014 no relevant memories found`);
|
|
3628
|
+
return { messages, estimatedTokens: 0 };
|
|
3629
|
+
}
|
|
3630
|
+
const memoryIndex = memories.map((m) => {
|
|
3631
|
+
const heat = typeof m.heat === "number" ? m.heat.toFixed(2) : "?";
|
|
3632
|
+
const type = m.memory_type || "unknown";
|
|
3633
|
+
const preview = typeof m.content === "string" ? m.content.slice(0, 120).replace(/\n/g, " ") : "(no preview)";
|
|
3634
|
+
return `- [${type}|h:${heat}] ${preview}${m.content?.length > 120 ? "\u2026" : ""}`;
|
|
3635
|
+
}).join("\n");
|
|
3636
|
+
const indexMessage = {
|
|
3637
|
+
role: "system",
|
|
3638
|
+
content: `<sulcus_memory_index count="${memories.length}" note="These memories are stored in Sulcus and recoverable via memory_recall. If context is tight, content already stored here can be safely summarized.">
|
|
3639
|
+
${memoryIndex}
|
|
3640
|
+
</sulcus_memory_index>`
|
|
3641
|
+
};
|
|
3642
|
+
let totalChars = 0;
|
|
3643
|
+
for (const msg of messages) {
|
|
3644
|
+
if (typeof msg.content === "string") totalChars += msg.content.length;
|
|
3645
|
+
else if (Array.isArray(msg.content)) {
|
|
3646
|
+
for (const part of msg.content) {
|
|
3647
|
+
if (typeof part === "string") totalChars += part.length;
|
|
3648
|
+
else if (part?.text) totalChars += part.text.length;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
totalChars += indexMessage.content.length;
|
|
3653
|
+
const estimatedTokens = Math.ceil(totalChars / this.t.charsPerToken);
|
|
3654
|
+
if (!tokenBudget || estimatedTokens < tokenBudget * this.t.assemblyInjectRatio) {
|
|
3655
|
+
const injections = [indexMessage];
|
|
3656
|
+
if (tokenBudget && estimatedTokens < tokenBudget * this.t.assemblyRecallRatio) {
|
|
3657
|
+
const topMemories = memories.slice(0, 3).filter(
|
|
3658
|
+
(m) => typeof m.content === "string" && m.content.length > 50 && m.content.length < 2e3
|
|
3659
|
+
);
|
|
3660
|
+
if (topMemories.length > 0) {
|
|
3661
|
+
const recalledContent = topMemories.map((m) => {
|
|
3662
|
+
const type = m.memory_type || "unknown";
|
|
3663
|
+
const heat = typeof m.heat === "number" ? m.heat.toFixed(2) : "?";
|
|
3664
|
+
return `[${type}|h:${heat}] ${m.content}`;
|
|
3665
|
+
}).join("\n---\n");
|
|
3666
|
+
const recalledChars = recalledContent.length;
|
|
3667
|
+
const recalledTokens = Math.ceil(recalledChars / this.t.charsPerToken);
|
|
3668
|
+
if (estimatedTokens + recalledTokens < tokenBudget * this.t.assemblyRecallCapRatio) {
|
|
3669
|
+
injections.push({
|
|
3670
|
+
role: "system",
|
|
3671
|
+
content: `<sulcus_recalled_context note="Relevant memories recalled from Sulcus for this conversation.">
|
|
3672
|
+
${recalledContent}
|
|
3673
|
+
</sulcus_recalled_context>`
|
|
3674
|
+
});
|
|
3675
|
+
totalChars += recalledChars;
|
|
3676
|
+
this.logger.debug(`sulcus-ce: assemble \u2014 injected ${topMemories.length} full recalled memories (+${recalledTokens} tokens)`);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
const firstNonSystem2 = messages.findIndex((m) => m.role !== "system");
|
|
3681
|
+
const insertAt2 = firstNonSystem2 === -1 ? messages.length : firstNonSystem2;
|
|
3682
|
+
const finalTokens = Math.ceil(totalChars / this.t.charsPerToken);
|
|
3683
|
+
const assembled2 = [
|
|
3684
|
+
...messages.slice(0, insertAt2),
|
|
3685
|
+
...injections,
|
|
3686
|
+
...messages.slice(insertAt2)
|
|
3687
|
+
];
|
|
3688
|
+
this.logger.debug(`sulcus-ce: assemble \u2014 injected ${memories.length} memory refs (${finalTokens} est tokens, under budget)`);
|
|
3689
|
+
return { messages: assembled2, estimatedTokens: finalTokens };
|
|
3690
|
+
}
|
|
3691
|
+
const storedFingerprints = /* @__PURE__ */ new Set();
|
|
3692
|
+
for (const m of memories) {
|
|
3693
|
+
if (typeof m.content === "string" && m.content.length > 50) {
|
|
3694
|
+
storedFingerprints.add(m.content.slice(0, 100).trim().toLowerCase());
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
const compressed = messages.map((msg) => {
|
|
3698
|
+
if (msg.role === "system" || msg.role === "user") return msg;
|
|
3699
|
+
if (typeof msg.content !== "string" || msg.content.length < 500) return msg;
|
|
3700
|
+
const fingerprint = msg.content.slice(0, 100).trim().toLowerCase();
|
|
3701
|
+
if (storedFingerprints.has(fingerprint)) {
|
|
3702
|
+
const summary = msg.content.slice(0, 200).replace(/\n/g, " ");
|
|
3703
|
+
return {
|
|
3704
|
+
...msg,
|
|
3705
|
+
content: `[stored in sulcus \u2014 use memory_recall to retrieve] ${summary}\u2026`
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
return msg;
|
|
3709
|
+
});
|
|
3710
|
+
let compressedChars = 0;
|
|
3711
|
+
for (const msg of compressed) {
|
|
3712
|
+
if (typeof msg.content === "string") compressedChars += msg.content.length;
|
|
3713
|
+
}
|
|
3714
|
+
compressedChars += indexMessage.content.length;
|
|
3715
|
+
const compressedTokens = Math.ceil(compressedChars / this.t.charsPerToken);
|
|
3716
|
+
const firstNonSystem = compressed.findIndex((m) => m.role !== "system");
|
|
3717
|
+
const insertAt = firstNonSystem === -1 ? compressed.length : firstNonSystem;
|
|
3718
|
+
const assembled = [
|
|
3719
|
+
...compressed.slice(0, insertAt),
|
|
3720
|
+
indexMessage,
|
|
3721
|
+
...compressed.slice(insertAt)
|
|
3722
|
+
];
|
|
3723
|
+
const savedTokens = estimatedTokens - compressedTokens;
|
|
3724
|
+
this.logger.info(`sulcus-ce: assemble \u2014 memory-aware compression saved ~${savedTokens} tokens (${estimatedTokens} \u2192 ${compressedTokens})`);
|
|
3725
|
+
return { messages: assembled, estimatedTokens: compressedTokens };
|
|
3726
|
+
} catch (e) {
|
|
3727
|
+
this.logger.warn(`sulcus-ce: assemble memory-aware failed: ${e} \u2014 falling back to passthrough`);
|
|
3728
|
+
return { messages: params.messages, estimatedTokens: 0 };
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
// ---------------------------------------------------------------------------
|
|
3732
|
+
// Constructive Assembly — Phase 6
|
|
3733
|
+
// Builds context deterministically: system messages + memory injection +
|
|
3734
|
+
// recent turns at full fidelity + older turns with summaries. Always fits
|
|
3735
|
+
// within tokenBudget. No transcript patching.
|
|
3736
|
+
// ---------------------------------------------------------------------------
|
|
3737
|
+
/**
|
|
3738
|
+
* Build context from working memory cache + recent turns.
|
|
3739
|
+
*
|
|
3740
|
+
* - System messages: pass through unchanged
|
|
3741
|
+
* - Recent N turns: pass through at full fidelity (agent needs recent context verbatim)
|
|
3742
|
+
* - Older tool results: replaced with their cached summary
|
|
3743
|
+
* - Older assistant messages: keep decisions/actions, compress verbose reasoning
|
|
3744
|
+
* - Memory injection: relevant recalled memories woven in
|
|
3745
|
+
* - N is budget-driven: calculated from tokenBudget minus system + memory overhead
|
|
3746
|
+
*/
|
|
3747
|
+
async assembleConstructive(params) {
|
|
3748
|
+
const { messages, tokenBudget, sessionId } = params;
|
|
3749
|
+
if (!messages || messages.length === 0) {
|
|
3750
|
+
return { messages: [], estimatedTokens: 0 };
|
|
3751
|
+
}
|
|
3752
|
+
if (!tokenBudget) {
|
|
3753
|
+
return { messages, estimatedTokens: 0 };
|
|
3754
|
+
}
|
|
3755
|
+
const sessionCache = this.getSessionCache(sessionId);
|
|
3756
|
+
try {
|
|
3757
|
+
const systemMsgs = messages.filter((m) => m.role === "system");
|
|
3758
|
+
const conversationMsgs = messages.filter((m) => m.role !== "system");
|
|
3759
|
+
let systemChars = 0;
|
|
3760
|
+
for (const msg of systemMsgs) {
|
|
3761
|
+
systemChars += this.estimateMessageChars(msg);
|
|
3762
|
+
}
|
|
3763
|
+
const systemTokens = Math.ceil(systemChars / this.t.charsPerToken);
|
|
3764
|
+
let memoryBlock = null;
|
|
3765
|
+
let memoryTokens = 0;
|
|
3766
|
+
if (this.memoryClient) {
|
|
3767
|
+
try {
|
|
3768
|
+
const recentUser = conversationMsgs.filter((m) => m.role === "user" && typeof m.content === "string").slice(-3);
|
|
3769
|
+
if (recentUser.length > 0) {
|
|
3770
|
+
const topicText = recentUser.map((m) => m.content).join(" ").slice(0, 500);
|
|
3771
|
+
const searchRes = await this.memoryClient.search_memory(topicText, 5, this.namespace);
|
|
3772
|
+
const memories = searchRes?.results ?? [];
|
|
3773
|
+
if (memories.length > 0) {
|
|
3774
|
+
const memoryContent = memories.map((m) => {
|
|
3775
|
+
const type = m.memory_type || "?";
|
|
3776
|
+
const heat = typeof m.heat === "number" ? m.heat.toFixed(2) : "?";
|
|
3777
|
+
const content = typeof m.content === "string" ? m.content.slice(0, 300) : "";
|
|
3778
|
+
return `[${type}|h:${heat}] ${content}`;
|
|
3779
|
+
}).join("\n");
|
|
3780
|
+
memoryBlock = {
|
|
3781
|
+
role: "system",
|
|
3782
|
+
content: `<sulcus_context note="Relevant memories recalled from Sulcus.">
|
|
3783
|
+
${memoryContent}
|
|
3784
|
+
</sulcus_context>`
|
|
3785
|
+
};
|
|
3786
|
+
memoryTokens = Math.ceil(memoryBlock.content.length / this.t.charsPerToken);
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
} catch {
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
const conversationBudget = tokenBudget - systemTokens - memoryTokens;
|
|
3793
|
+
if (conversationBudget <= 0) {
|
|
3794
|
+
this.logger.warn(`sulcus-ce: constructive \u2014 budget exhausted by system messages (${systemTokens} tokens)`);
|
|
3795
|
+
const assembled2 = [...systemMsgs, ...memoryBlock ? [memoryBlock] : [], ...conversationMsgs.slice(-2)];
|
|
3796
|
+
return { messages: assembled2, estimatedTokens: systemTokens + memoryTokens };
|
|
3797
|
+
}
|
|
3798
|
+
const turns = [];
|
|
3799
|
+
let currentTurnStart = 0;
|
|
3800
|
+
for (let i = 0; i < conversationMsgs.length; i++) {
|
|
3801
|
+
if (i > 0 && conversationMsgs[i].role === "user") {
|
|
3802
|
+
turns.push({
|
|
3803
|
+
startIdx: currentTurnStart,
|
|
3804
|
+
endIdx: i - 1,
|
|
3805
|
+
messages: conversationMsgs.slice(currentTurnStart, i)
|
|
3806
|
+
});
|
|
3807
|
+
currentTurnStart = i;
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
if (currentTurnStart < conversationMsgs.length) {
|
|
3811
|
+
turns.push({
|
|
3812
|
+
startIdx: currentTurnStart,
|
|
3813
|
+
endIdx: conversationMsgs.length - 1,
|
|
3814
|
+
messages: conversationMsgs.slice(currentTurnStart)
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
if (turns.length === 0) {
|
|
3818
|
+
const assembled2 = [...systemMsgs, ...memoryBlock ? [memoryBlock] : []];
|
|
3819
|
+
return { messages: assembled2, estimatedTokens: systemTokens + memoryTokens };
|
|
3820
|
+
}
|
|
3821
|
+
const recentBudgetRatio = this.t.constructiveRecentBudgetRatio;
|
|
3822
|
+
const recentBudgetChars = conversationBudget * this.t.charsPerToken * recentBudgetRatio;
|
|
3823
|
+
let recentChars = 0;
|
|
3824
|
+
let recentTurnCount = 0;
|
|
3825
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
3826
|
+
let turnChars = 0;
|
|
3827
|
+
for (const msg of turns[i].messages) {
|
|
3828
|
+
turnChars += this.estimateMessageChars(msg);
|
|
3829
|
+
}
|
|
3830
|
+
if (recentChars + turnChars > recentBudgetChars && recentTurnCount >= this.t.constructiveMinRecentTurns) {
|
|
3831
|
+
break;
|
|
3832
|
+
}
|
|
3833
|
+
recentChars += turnChars;
|
|
3834
|
+
recentTurnCount++;
|
|
3835
|
+
}
|
|
3836
|
+
recentTurnCount = Math.max(recentTurnCount, Math.min(this.t.constructiveMinRecentTurns, turns.length));
|
|
3837
|
+
const olderTurns = turns.slice(0, turns.length - recentTurnCount);
|
|
3838
|
+
const recentTurns = turns.slice(turns.length - recentTurnCount);
|
|
3839
|
+
const pointerSummaries = /* @__PURE__ */ new Map();
|
|
3840
|
+
if (this.memoryClient && olderTurns.length > 0) {
|
|
3841
|
+
try {
|
|
3842
|
+
const searchRes = await this.memoryClient.search_memory(
|
|
3843
|
+
`tool-result session:${sessionId}`,
|
|
3844
|
+
20,
|
|
3845
|
+
this.namespace
|
|
3846
|
+
);
|
|
3847
|
+
for (const r of searchRes?.results ?? []) {
|
|
3848
|
+
const summary = r.pointer_summary || r.label;
|
|
3849
|
+
if (!summary || typeof summary !== "string") continue;
|
|
3850
|
+
const keyPoints = r.key_points ?? [];
|
|
3851
|
+
for (const kp of keyPoints) {
|
|
3852
|
+
if (typeof kp === "string" && kp.startsWith("msg:")) {
|
|
3853
|
+
pointerSummaries.set(kp.slice(4), summary);
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
if (pointerSummaries.size > 0) {
|
|
3858
|
+
this.logger.debug(`sulcus-ce: constructive \u2014 fetched ${pointerSummaries.size} pointer summaries from Sulcus`);
|
|
3859
|
+
}
|
|
3860
|
+
} catch {
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
const remainingBudgetChars = conversationBudget * this.t.charsPerToken - recentChars;
|
|
3864
|
+
const summarizedOlder = [];
|
|
3865
|
+
let olderChars = 0;
|
|
3866
|
+
for (const turn of olderTurns) {
|
|
3867
|
+
for (const msg of turn.messages) {
|
|
3868
|
+
let processed = msg;
|
|
3869
|
+
if (msg.role === "tool" && typeof msg.content === "string" && msg.id) {
|
|
3870
|
+
const cached = sessionCache.get(msg.id);
|
|
3871
|
+
if (cached?.sulcusNodeId && pointerSummaries.has(msg.id)) {
|
|
3872
|
+
processed = {
|
|
3873
|
+
...msg,
|
|
3874
|
+
content: `[${cached.toolName} summary] ${pointerSummaries.get(msg.id)}`
|
|
3875
|
+
};
|
|
3876
|
+
} else if (cached) {
|
|
3877
|
+
if (cached.sulcusNodeId) {
|
|
3878
|
+
this.logger.warn(
|
|
3879
|
+
`sulcus-ce: constructive \u2014 pointer_summary not found for cached tool result (${cached.toolName}, node=${cached.sulcusNodeId}, msg=${msg.id}). Falling back to truncation.`
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
const preview = msg.content.length > 500 ? msg.content.slice(0, 250) + "\n\u2026\n" + msg.content.slice(-250) : msg.content;
|
|
3883
|
+
processed = {
|
|
3884
|
+
...msg,
|
|
3885
|
+
content: `[${cached.toolName}] ${preview}`
|
|
3886
|
+
};
|
|
3887
|
+
} else if (msg.content.length > 500) {
|
|
3888
|
+
const toolName = msg.name || "tool";
|
|
3889
|
+
const preview = msg.content.slice(0, 250) + "\n\u2026\n" + msg.content.slice(-250);
|
|
3890
|
+
processed = {
|
|
3891
|
+
...msg,
|
|
3892
|
+
content: `[${toolName}] ${preview}`
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
} else if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.length > 1e3) {
|
|
3896
|
+
processed = {
|
|
3897
|
+
...msg,
|
|
3898
|
+
content: this.compressAssistantMessage(msg.content)
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
const processedChars = this.estimateMessageChars(processed);
|
|
3902
|
+
if (olderChars + processedChars > remainingBudgetChars) {
|
|
3903
|
+
break;
|
|
3904
|
+
}
|
|
3905
|
+
summarizedOlder.push(processed);
|
|
3906
|
+
olderChars += processedChars;
|
|
3907
|
+
}
|
|
3908
|
+
if (olderChars >= remainingBudgetChars) break;
|
|
3909
|
+
}
|
|
3910
|
+
const recentMessages = recentTurns.flatMap((t) => t.messages);
|
|
3911
|
+
const assembled = [
|
|
3912
|
+
...systemMsgs,
|
|
3913
|
+
...memoryBlock ? [memoryBlock] : [],
|
|
3914
|
+
...summarizedOlder,
|
|
3915
|
+
...recentMessages
|
|
3916
|
+
];
|
|
3917
|
+
const totalChars = systemChars + (memoryBlock ? memoryBlock.content.length : 0) + olderChars + recentChars;
|
|
3918
|
+
const estimatedTokens = Math.ceil(totalChars / this.t.charsPerToken);
|
|
3919
|
+
this.logger.info(
|
|
3920
|
+
`sulcus-ce: constructive assembly \u2014 ${assembled.length} messages, ${estimatedTokens}/${tokenBudget} tokens, ${recentTurnCount} recent turns (full), ${olderTurns.length} older (summarized), ${sessionCache.size} cached summaries`
|
|
3921
|
+
);
|
|
3922
|
+
return { messages: assembled, estimatedTokens };
|
|
3923
|
+
} catch (e) {
|
|
3924
|
+
this.logger.warn(`sulcus-ce: constructive assembly failed: ${e} \u2014 falling back to passthrough`);
|
|
3925
|
+
return { messages, estimatedTokens: 0 };
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
/** Estimate character count of a message (handles string + multipart content). */
|
|
3929
|
+
estimateMessageChars(msg) {
|
|
3930
|
+
if (typeof msg.content === "string") return msg.content.length;
|
|
3931
|
+
if (Array.isArray(msg.content)) {
|
|
3932
|
+
let total = 0;
|
|
3933
|
+
for (const part of msg.content) {
|
|
3934
|
+
if (typeof part === "string") total += part.length;
|
|
3935
|
+
else if (part?.text) total += part.text.length;
|
|
3936
|
+
}
|
|
3937
|
+
return total;
|
|
3938
|
+
}
|
|
3939
|
+
return 0;
|
|
3940
|
+
}
|
|
3941
|
+
/**
|
|
3942
|
+
* Compress a verbose assistant message: keep decision sentences, trim reasoning.
|
|
3943
|
+
* Returns the compressed content (200–600 chars).
|
|
3944
|
+
*/
|
|
3945
|
+
compressAssistantMessage(content) {
|
|
3946
|
+
const sentences = content.split(/(?<=[.!?\n])\s+/).filter((s) => s.trim().length > 10);
|
|
3947
|
+
const decisionSentences = sentences.filter((s) => {
|
|
3948
|
+
const lc = s.toLowerCase();
|
|
3949
|
+
return DECISION_MARKERS.some((m) => lc.includes(m));
|
|
3950
|
+
});
|
|
3951
|
+
const maxSentence = this.t.compressSentenceMaxChars;
|
|
3952
|
+
const maxOutput = this.t.compressMaxChars;
|
|
3953
|
+
const kept = [];
|
|
3954
|
+
if (sentences.length > 0) kept.push(sentences[0].slice(0, maxSentence));
|
|
3955
|
+
for (const ds of decisionSentences.slice(0, 3)) {
|
|
3956
|
+
if (!kept.includes(ds.slice(0, maxSentence))) kept.push(ds.slice(0, maxSentence));
|
|
3957
|
+
}
|
|
3958
|
+
if (sentences.length > 1) {
|
|
3959
|
+
const last = sentences[sentences.length - 1].slice(0, maxSentence);
|
|
3960
|
+
if (!kept.includes(last)) kept.push(last);
|
|
3961
|
+
}
|
|
3962
|
+
if (kept.length === 0) {
|
|
3963
|
+
return content.slice(0, maxSentence * 2) + "\u2026";
|
|
3964
|
+
}
|
|
3965
|
+
const compressed = kept.join(" ");
|
|
3966
|
+
if (compressed.length > maxOutput) return compressed.slice(0, maxOutput - 3) + "\u2026";
|
|
3967
|
+
return compressed;
|
|
3968
|
+
}
|
|
3969
|
+
// ---------------------------------------------------------------------------
|
|
3970
|
+
// Compact — Phase 5: Unified Compaction (Capture + Enrich + Delegate)
|
|
3971
|
+
// ---------------------------------------------------------------------------
|
|
3972
|
+
async compact(params) {
|
|
3973
|
+
if (this.compactMode !== "smart" || !this.memoryClient) {
|
|
3974
|
+
this.logger.debug("sulcus-ce: compact() \u2014 no memory client or passthrough mode, delegating plain");
|
|
3975
|
+
try {
|
|
3976
|
+
return await this.delegateCompaction(params);
|
|
3977
|
+
} catch (e) {
|
|
3978
|
+
return { ok: false, compacted: false, reason: `delegation-error: ${e}` };
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
try {
|
|
3982
|
+
try {
|
|
3983
|
+
const runtimeMessages = params.runtimeContext?.messages;
|
|
3984
|
+
if (Array.isArray(runtimeMessages) && runtimeMessages.length > 0) {
|
|
3985
|
+
const firstUser = runtimeMessages.find((m) => m.role === "user" && typeof m.content === "string");
|
|
3986
|
+
const lastAssistant = [...runtimeMessages].reverse().find((m) => m.role === "assistant" && typeof m.content === "string");
|
|
3987
|
+
const summaryParts = [
|
|
3988
|
+
`Pre-compaction capture (${runtimeMessages.length} messages)`,
|
|
3989
|
+
`Topic: ${typeof firstUser?.content === "string" ? firstUser.content.substring(0, 200) : "(none)"}`,
|
|
3990
|
+
`Last output: ${typeof lastAssistant?.content === "string" ? lastAssistant.content.substring(0, 200) : "(none)"}`
|
|
3991
|
+
];
|
|
3992
|
+
await this.memoryClient.add_memory(summaryParts.join("\n"), "episodic", {
|
|
3993
|
+
key_points: [`session:${params.sessionId}`, "compaction-capture"]
|
|
3994
|
+
});
|
|
3995
|
+
this.logger.info(`sulcus-ce: compact \u2014 pre-compaction capture stored (${runtimeMessages.length} messages)`);
|
|
3996
|
+
}
|
|
3997
|
+
} catch (captureErr) {
|
|
3998
|
+
this.logger.debug(`sulcus-ce: compact \u2014 pre-compaction capture failed: ${captureErr}`);
|
|
3999
|
+
}
|
|
4000
|
+
const searchRes = await this.memoryClient.search_memory(
|
|
4001
|
+
"recent conversation context decisions tasks",
|
|
4002
|
+
12,
|
|
4003
|
+
this.namespace
|
|
4004
|
+
);
|
|
4005
|
+
const storedMemories = searchRes?.results ?? [];
|
|
4006
|
+
if (storedMemories.length === 0) {
|
|
4007
|
+
this.logger.debug("sulcus-ce: compact \u2014 no stored memories, delegating plain");
|
|
4008
|
+
return await this.delegateCompaction(params);
|
|
4009
|
+
}
|
|
4010
|
+
const storedSummary = storedMemories.map((m) => {
|
|
4011
|
+
const type = m.memory_type || "unknown";
|
|
4012
|
+
const heat = typeof m.heat === "number" ? m.heat.toFixed(2) : "?";
|
|
4013
|
+
const preview = typeof m.content === "string" ? m.content.slice(0, 150).replace(/\n/g, " ") : "(no content)";
|
|
4014
|
+
return ` - [${type}, heat=${heat}] ${preview}`;
|
|
4015
|
+
}).join("\n");
|
|
4016
|
+
const existingInstructions = params.customInstructions || "";
|
|
4017
|
+
const smartInstructions = [
|
|
4018
|
+
existingInstructions,
|
|
4019
|
+
"",
|
|
4020
|
+
"=== SULCUS MEMORY CONTEXT ===",
|
|
4021
|
+
"The following content is ALREADY stored in the agent's persistent memory (Sulcus)",
|
|
4022
|
+
"and is recoverable via memory_recall. You do NOT need to preserve this content",
|
|
4023
|
+
"in the summary \u2014 it will survive compaction through memory.",
|
|
4024
|
+
"",
|
|
4025
|
+
storedSummary,
|
|
4026
|
+
"",
|
|
4027
|
+
"=== COMPACTION GUIDANCE ===",
|
|
4028
|
+
"Focus the summary on:",
|
|
4029
|
+
"1. ACTIVE TASK STATE \u2014 what the agent is currently working on, next steps",
|
|
4030
|
+
"2. PENDING DECISIONS \u2014 anything awaiting input or approval",
|
|
4031
|
+
"3. UNFINISHED WORK \u2014 partial progress, blockers, what remains",
|
|
4032
|
+
"4. CONVERSATION DYNAMICS \u2014 who asked what, tone, important agreements",
|
|
4033
|
+
"",
|
|
4034
|
+
"DO NOT re-summarize content already listed above as stored in memory.",
|
|
4035
|
+
"Instead, note: '[stored in Sulcus \u2014 recallable via memory_recall]'",
|
|
4036
|
+
"",
|
|
4037
|
+
"Structure the summary with clear sections when appropriate:",
|
|
4038
|
+
"- Active Context (what's happening now)",
|
|
4039
|
+
"- Stored in Memory (brief note of what's recallable)",
|
|
4040
|
+
"- Key Decisions & Agreements",
|
|
4041
|
+
"- Next Steps"
|
|
4042
|
+
].filter(Boolean).join("\n");
|
|
4043
|
+
this.logger.info(
|
|
4044
|
+
`sulcus-ce: smart compaction \u2014 ${storedMemories.length} memories in context, enriched instructions (${smartInstructions.length} chars)`
|
|
4045
|
+
);
|
|
4046
|
+
const result = await this.delegateCompaction({
|
|
4047
|
+
...params,
|
|
4048
|
+
customInstructions: smartInstructions
|
|
4049
|
+
});
|
|
4050
|
+
if (result.compacted) {
|
|
4051
|
+
const saved = (result.result?.tokensBefore ?? 0) - (result.result?.tokensAfter ?? 0);
|
|
4052
|
+
this.logger.info(`sulcus-ce: smart compaction saved ~${saved} tokens`);
|
|
4053
|
+
}
|
|
4054
|
+
return result;
|
|
4055
|
+
} catch (e) {
|
|
4056
|
+
this.logger.warn(`sulcus-ce: smart compaction failed: ${e} \u2014 falling back to plain delegation`);
|
|
4057
|
+
try {
|
|
4058
|
+
return await this.delegateCompaction(params);
|
|
4059
|
+
} catch (e2) {
|
|
4060
|
+
return { ok: false, compacted: false, reason: `delegation-error: ${e2}` };
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
// ---------------------------------------------------------------------------
|
|
4065
|
+
// Subagent lifecycle — no-op
|
|
4066
|
+
// ---------------------------------------------------------------------------
|
|
4067
|
+
async prepareSubagentSpawn(_params) {
|
|
4068
|
+
return void 0;
|
|
4069
|
+
}
|
|
4070
|
+
async onSubagentEnded(params) {
|
|
4071
|
+
const sessionId = params?.sessionId;
|
|
4072
|
+
if (sessionId && this.turnCounter.has(sessionId)) {
|
|
4073
|
+
this.clearSession(sessionId);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
// ---------------------------------------------------------------------------
|
|
4077
|
+
// Cleanup
|
|
4078
|
+
// ---------------------------------------------------------------------------
|
|
4079
|
+
async dispose() {
|
|
4080
|
+
const allSessions = /* @__PURE__ */ new Set([
|
|
4081
|
+
...this.turnCounter.keys(),
|
|
4082
|
+
...this.workingMemory.keys(),
|
|
4083
|
+
...this.sessionLastActivity.keys()
|
|
4084
|
+
]);
|
|
4085
|
+
for (const sessionId of allSessions) {
|
|
4086
|
+
this.clearSession(sessionId);
|
|
4087
|
+
}
|
|
4088
|
+
this.logger.info("sulcus-ce: disposed");
|
|
4089
|
+
}
|
|
4090
|
+
};
|
|
4091
|
+
|
|
2647
4092
|
// index.ts
|
|
2648
4093
|
function generateSessionId() {
|
|
2649
4094
|
const ts = Date.now().toString(36);
|
|
@@ -2878,6 +4323,7 @@ var recallQM = {
|
|
|
2878
4323
|
scoreTurns: 0
|
|
2879
4324
|
};
|
|
2880
4325
|
var wasJustCompacted = false;
|
|
4326
|
+
var contextEngineActive = false;
|
|
2881
4327
|
var REBUILD_TOKEN_BUDGET = 1e4;
|
|
2882
4328
|
var CORE_MEMORY_MAX_CHARS = 4e3;
|
|
2883
4329
|
var coreMemoryCache = void 0;
|
|
@@ -3325,6 +4771,10 @@ ${contextParts.join("\n")}
|
|
|
3325
4771
|
const messages = Array.isArray(event?.messages) ? event.messages : [];
|
|
3326
4772
|
if (messages.length === 0) return;
|
|
3327
4773
|
wasJustCompacted = true;
|
|
4774
|
+
if (contextEngineActive) {
|
|
4775
|
+
logger.info("sulcus: pre_compaction_capture \u2014 skipped (context engine handles capture). Rebuild flag SET.");
|
|
4776
|
+
return;
|
|
4777
|
+
}
|
|
3328
4778
|
logger.info("sulcus: pre_compaction_capture \u2014 rebuild flag SET (next turn will inject full Sulcus context)");
|
|
3329
4779
|
const firstUser = messages.find((m) => m.role === "user" || m.type === "human");
|
|
3330
4780
|
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant" || m.type === "ai");
|
|
@@ -3335,7 +4785,7 @@ ${contextParts.join("\n")}
|
|
|
3335
4785
|
const decisions = [];
|
|
3336
4786
|
const errors = [];
|
|
3337
4787
|
const userIntents = [];
|
|
3338
|
-
const
|
|
4788
|
+
const DECISION_MARKERS2 = ["decided", "will use", "going to", "plan is", "the fix", "conclusion", "recommend", "approach"];
|
|
3339
4789
|
const ERROR_MARKERS = ["error:", "failed:", "exception", "traceback", "panicked", "stack trace"];
|
|
3340
4790
|
for (const msg of messages) {
|
|
3341
4791
|
const role = msg.role ?? msg.type;
|
|
@@ -3345,10 +4795,10 @@ ${contextParts.join("\n")}
|
|
|
3345
4795
|
}
|
|
3346
4796
|
if ((role === "assistant" || role === "ai") && rawContent.length > 20) {
|
|
3347
4797
|
const lc = rawContent.toLowerCase();
|
|
3348
|
-
if (
|
|
4798
|
+
if (DECISION_MARKERS2.some((m) => lc.includes(m))) {
|
|
3349
4799
|
const sentences = rawContent.split(/[.!?\n]/).filter((s) => s.trim().length > 10);
|
|
3350
4800
|
for (const s of sentences) {
|
|
3351
|
-
if (
|
|
4801
|
+
if (DECISION_MARKERS2.some((m) => s.toLowerCase().includes(m)) && !decisions.includes(s.trim())) {
|
|
3352
4802
|
decisions.push(s.trim().substring(0, 200));
|
|
3353
4803
|
if (decisions.length >= 5) break;
|
|
3354
4804
|
}
|
|
@@ -3520,7 +4970,7 @@ var SulcusCloudClient = class _SulcusCloudClient {
|
|
|
3520
4970
|
_rawRequest(method, path, bodyStr, parsedUrl) {
|
|
3521
4971
|
return new Promise((resolveP, rejectP) => {
|
|
3522
4972
|
const isHttps = parsedUrl.protocol === "https:";
|
|
3523
|
-
const transport = isHttps ? https :
|
|
4973
|
+
const transport = isHttps ? https : http2;
|
|
3524
4974
|
const headers = {
|
|
3525
4975
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
3526
4976
|
"Accept": "application/json"
|
|
@@ -3572,7 +5022,7 @@ var SulcusCloudClient = class _SulcusCloudClient {
|
|
|
3572
5022
|
request(method, path, body) {
|
|
3573
5023
|
let parsedUrl;
|
|
3574
5024
|
try {
|
|
3575
|
-
parsedUrl = new
|
|
5025
|
+
parsedUrl = new import_node_url2.URL(this.serverUrl + path);
|
|
3576
5026
|
} catch (e) {
|
|
3577
5027
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3578
5028
|
return Promise.reject(new Error(`SulcusCloudClient: invalid URL ${this.serverUrl}${path}: ${msg}`));
|
|
@@ -4163,7 +5613,7 @@ var ASSISTANT_CAPTURE_MAX_DIRECT = 1500;
|
|
|
4163
5613
|
function summarizeForCapture(text, namespace) {
|
|
4164
5614
|
const paragraphs = text.split(/\n{2,}/).map((p) => p.trim()).filter((p) => p.length > 20);
|
|
4165
5615
|
if (paragraphs.length === 0) return text.substring(0, ASSISTANT_CAPTURE_MAX_DIRECT);
|
|
4166
|
-
const
|
|
5616
|
+
const DECISION_MARKERS2 = [
|
|
4167
5617
|
"decided",
|
|
4168
5618
|
"recommend",
|
|
4169
5619
|
"conclusion",
|
|
@@ -4185,7 +5635,7 @@ function summarizeForCapture(text, namespace) {
|
|
|
4185
5635
|
if (paragraphs[0]) keyParagraphs.push(paragraphs[0]);
|
|
4186
5636
|
for (let i = 1; i < paragraphs.length - 1; i++) {
|
|
4187
5637
|
const pLower = paragraphs[i].toLowerCase();
|
|
4188
|
-
if (
|
|
5638
|
+
if (DECISION_MARKERS2.some((m) => pLower.includes(m))) {
|
|
4189
5639
|
keyParagraphs.push(paragraphs[i]);
|
|
4190
5640
|
if (keyParagraphs.length >= 3) break;
|
|
4191
5641
|
}
|
|
@@ -4627,7 +6077,40 @@ async function expandQueryWithEntities(client, originalQuery, namespace, logger)
|
|
|
4627
6077
|
return { extraMemories, expandedQuery };
|
|
4628
6078
|
}
|
|
4629
6079
|
var THIN_RECALL_THRESHOLD = 3;
|
|
4630
|
-
function buildSdkRecallHandler(sulcusMem, namespace, maxResults, profileFrequency, logger, boostOnRecall = true, tokenBudget = 1e4, contextRebuild = true, contextWindowSize = 2e5) {
|
|
6080
|
+
function buildSdkRecallHandler(sulcusMem, namespace, maxResults, profileFrequency, logger, boostOnRecall = true, tokenBudget = 1e4, contextRebuild = true, contextWindowSize = 2e5, localClient = null) {
|
|
6081
|
+
async function localFirstSearch(query, limit, ns) {
|
|
6082
|
+
if (!localClient || !localClient.isAvailable()) {
|
|
6083
|
+
const res2 = await sulcusMem.search_memory(query, limit, ns);
|
|
6084
|
+
return { results: res2?.results ?? [], source: "cloud" };
|
|
6085
|
+
}
|
|
6086
|
+
try {
|
|
6087
|
+
const localRes = await localClient.search_memory(query, limit, ns);
|
|
6088
|
+
const localResults = localRes?.results ?? [];
|
|
6089
|
+
if (localResults.length >= Math.ceil(limit * 0.7)) {
|
|
6090
|
+
sulcusMem.search_memory(query, limit, ns).catch(() => {
|
|
6091
|
+
});
|
|
6092
|
+
return { results: localResults, source: "local" };
|
|
6093
|
+
}
|
|
6094
|
+
if (localResults.length > 0) {
|
|
6095
|
+
try {
|
|
6096
|
+
const remoteRes = await sulcusMem.search_memory(query, limit, ns);
|
|
6097
|
+
const remoteResults = remoteRes?.results ?? [];
|
|
6098
|
+
const remoteById = new Map(remoteResults.map((r) => [r.id, r]));
|
|
6099
|
+
const mergedMap = new Map(remoteById);
|
|
6100
|
+
for (const [, node] of new Map(localResults.map((r) => [r.id, r]))) {
|
|
6101
|
+
if (!mergedMap.has(node.id)) mergedMap.set(node.id, node);
|
|
6102
|
+
}
|
|
6103
|
+
return { results: [...mergedMap.values()].slice(0, limit), source: "local+cloud" };
|
|
6104
|
+
} catch {
|
|
6105
|
+
return { results: localResults, source: "local" };
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
} catch {
|
|
6109
|
+
logger.debug?.("sulcus: autoRecall local search failed, falling back to cloud");
|
|
6110
|
+
}
|
|
6111
|
+
const res = await sulcusMem.search_memory(query, limit, ns);
|
|
6112
|
+
return { results: res?.results ?? [], source: "cloud" };
|
|
6113
|
+
}
|
|
4631
6114
|
let turnCount = 0;
|
|
4632
6115
|
let profileCache = null;
|
|
4633
6116
|
let recallCache = null;
|
|
@@ -4767,7 +6250,7 @@ ${mutedComment}` : mutedComment };
|
|
|
4767
6250
|
logger.info(`sulcus: TOPIC SHIFT detected (overlap=${overlap.toFixed(2)}) \u2014 fresh recall (turn ${turnCount})`);
|
|
4768
6251
|
}
|
|
4769
6252
|
try {
|
|
4770
|
-
const searchRes = await
|
|
6253
|
+
const searchRes = await localFirstSearch(recallQuery, effectiveMax, effectiveNamespace);
|
|
4771
6254
|
const vectorResults = searchRes?.results ?? [];
|
|
4772
6255
|
let sdkExpanded = vectorResults;
|
|
4773
6256
|
if (vectorResults.length < THIN_RECALL_THRESHOLD) {
|
|
@@ -5144,6 +6627,7 @@ function buildPromptSection(params) {
|
|
|
5144
6627
|
lines.push("Memory types: episodic (events, fast decay), semantic (knowledge, slow), preference (opinions, slower), procedural (how-tos, slowest), fact (data, slow)");
|
|
5145
6628
|
return lines;
|
|
5146
6629
|
}
|
|
6630
|
+
var toolRecallCache = { results: [], cachedAt: 0 };
|
|
5147
6631
|
var toolDefinitions = {
|
|
5148
6632
|
memory_recall: {
|
|
5149
6633
|
schema: {
|
|
@@ -5157,14 +6641,70 @@ var toolDefinitions = {
|
|
|
5157
6641
|
})
|
|
5158
6642
|
},
|
|
5159
6643
|
options: { name: "memory_recall" },
|
|
5160
|
-
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, isAvailable }) => async (_id, params) => {
|
|
6644
|
+
makeExecute: ({ sulcusMem, localClient, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
5161
6645
|
if (!isAvailable || !sulcusMem) throw new Error(`Sulcus unavailable: ${nativeLoader.error || "not loaded"}`);
|
|
5162
6646
|
const searchNamespace = params.namespace ?? namespace;
|
|
5163
|
-
const
|
|
5164
|
-
const
|
|
6647
|
+
const query = params.query;
|
|
6648
|
+
const limit = params.limit ?? 5;
|
|
6649
|
+
let results = [];
|
|
6650
|
+
let source = "cloud";
|
|
6651
|
+
if (localClient && localClient.isAvailable()) {
|
|
6652
|
+
try {
|
|
6653
|
+
const localRes = await localClient.search_memory(query, limit, searchNamespace);
|
|
6654
|
+
const localResults = localRes?.results ?? [];
|
|
6655
|
+
if (localResults.length >= Math.ceil(limit * 0.7)) {
|
|
6656
|
+
results = localResults;
|
|
6657
|
+
source = "local";
|
|
6658
|
+
logger.debug?.(`sulcus: recall served from local (${localResults.length} results)`);
|
|
6659
|
+
sulcusMem.search_memory(query, limit, searchNamespace).catch(() => {
|
|
6660
|
+
});
|
|
6661
|
+
} else if (localResults.length > 0) {
|
|
6662
|
+
source = "local+cloud";
|
|
6663
|
+
try {
|
|
6664
|
+
const remoteRes = await sulcusMem.search_memory(query, limit, searchNamespace);
|
|
6665
|
+
const remoteResults = remoteRes?.results ?? [];
|
|
6666
|
+
const remoteById = new Map(remoteResults.map((r) => [r.id, r]));
|
|
6667
|
+
const localById = new Map(localResults.map((r) => [r.id, r]));
|
|
6668
|
+
const mergedMap = new Map(remoteById);
|
|
6669
|
+
for (const [id, node] of localById) {
|
|
6670
|
+
if (!mergedMap.has(id)) mergedMap.set(id, node);
|
|
6671
|
+
}
|
|
6672
|
+
const merged = [...mergedMap.values()];
|
|
6673
|
+
results = merged.slice(0, limit);
|
|
6674
|
+
logger.debug?.(`sulcus: recall merged local(${localResults.length}) + cloud(${remoteResults.length}) \u2192 ${results.length}`);
|
|
6675
|
+
} catch {
|
|
6676
|
+
results = localResults;
|
|
6677
|
+
source = "local";
|
|
6678
|
+
logger.debug?.(`sulcus: remote recall failed, using ${localResults.length} local results`);
|
|
6679
|
+
}
|
|
6680
|
+
} else {
|
|
6681
|
+
logger.debug?.("sulcus: local recall empty, falling back to cloud");
|
|
6682
|
+
}
|
|
6683
|
+
} catch (localErr) {
|
|
6684
|
+
logger.debug?.(`sulcus: local recall failed (${localErr}), falling back to cloud`);
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6687
|
+
if (results.length === 0) {
|
|
6688
|
+
try {
|
|
6689
|
+
const res = await sulcusMem.search_memory(query, limit, searchNamespace);
|
|
6690
|
+
results = res?.results ?? [];
|
|
6691
|
+
source = "cloud";
|
|
6692
|
+
} catch (cloudErr) {
|
|
6693
|
+
if (toolRecallCache.results.length > 0) {
|
|
6694
|
+
results = toolRecallCache.results;
|
|
6695
|
+
source = "stale-cache";
|
|
6696
|
+
logger.warn?.(`sulcus: both local and cloud recall failed, serving stale cache (${results.length} items)`);
|
|
6697
|
+
} else {
|
|
6698
|
+
throw cloudErr;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
if (results.length > 0 && source !== "stale-cache") {
|
|
6703
|
+
toolRecallCache = { results, cachedAt: Date.now() };
|
|
6704
|
+
}
|
|
5165
6705
|
return {
|
|
5166
6706
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
5167
|
-
details: { results, backend: backendMode, namespace: searchNamespace }
|
|
6707
|
+
details: { results, backend: backendMode, namespace: searchNamespace, source }
|
|
5168
6708
|
};
|
|
5169
6709
|
}
|
|
5170
6710
|
},
|
|
@@ -5186,7 +6726,7 @@ var toolDefinitions = {
|
|
|
5186
6726
|
})
|
|
5187
6727
|
},
|
|
5188
6728
|
options: { name: "memory_store" },
|
|
5189
|
-
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
6729
|
+
makeExecute: ({ sulcusMem, localClient, retryQueue, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
5190
6730
|
const content = params.content;
|
|
5191
6731
|
if (isJunkMemory(content)) {
|
|
5192
6732
|
logger.debug?.(`sulcus: filtered junk memory: "${content.substring(0, 50)}..."`);
|
|
@@ -5195,8 +6735,33 @@ var toolDefinitions = {
|
|
|
5195
6735
|
if (!isAvailable || !sulcusMem) throw new Error(`Sulcus unavailable: ${nativeLoader.error || "not loaded"}`);
|
|
5196
6736
|
const mtype = params.memory_type || "episodic";
|
|
5197
6737
|
const storeHints = buildExtractionHints(mtype, namespace, "user_capture", content.substring(0, 200));
|
|
5198
|
-
|
|
5199
|
-
|
|
6738
|
+
let nodeId = "unknown";
|
|
6739
|
+
let res = {};
|
|
6740
|
+
let storeSource = "cloud";
|
|
6741
|
+
if (localClient && localClient.isAvailable()) {
|
|
6742
|
+
try {
|
|
6743
|
+
const localRes = await localClient.add_memory(content, mtype, storeHints);
|
|
6744
|
+
nodeId = localRes?.id ?? "unknown";
|
|
6745
|
+
res = localRes;
|
|
6746
|
+
storeSource = "local";
|
|
6747
|
+
logger.debug?.(`sulcus: stored to local sidecar (id: ${nodeId})`);
|
|
6748
|
+
sulcusMem.add_memory(content, mtype, storeHints).then((remoteRes) => {
|
|
6749
|
+
logger.debug?.(`sulcus: remote store synced (local: ${nodeId}, remote: ${remoteRes?.id ?? "?"})`);
|
|
6750
|
+
}).catch((err) => {
|
|
6751
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6752
|
+
logger.warn(`sulcus: remote store failed for ${nodeId} (will retry): ${errMsg}`);
|
|
6753
|
+
retryQueue.enqueue(nodeId, "store", { content, memory_type: mtype, extraction_hints: storeHints });
|
|
6754
|
+
});
|
|
6755
|
+
} catch (localErr) {
|
|
6756
|
+
const errMsg = localErr instanceof Error ? localErr.message : String(localErr);
|
|
6757
|
+
logger.warn(`sulcus: local store failed (${errMsg}), falling back to cloud`);
|
|
6758
|
+
}
|
|
6759
|
+
}
|
|
6760
|
+
if (storeSource === "cloud") {
|
|
6761
|
+
const cloudRes = await sulcusMem.add_memory(content, mtype, storeHints);
|
|
6762
|
+
nodeId = cloudRes?.id ?? "unknown";
|
|
6763
|
+
res = cloudRes;
|
|
6764
|
+
}
|
|
5200
6765
|
let trainResult = null;
|
|
5201
6766
|
if (params.train === true) {
|
|
5202
6767
|
try {
|
|
@@ -5216,8 +6781,8 @@ var toolDefinitions = {
|
|
|
5216
6781
|
}
|
|
5217
6782
|
}
|
|
5218
6783
|
return {
|
|
5219
|
-
content: [{ type: "text", text: `Stored [${mtype}] memory (id: ${nodeId}) \u2192 backend: ${backendMode}, namespace: ${namespace}${trainResult ? ` | SIU: ${trainResult}` : ""}` }],
|
|
5220
|
-
details: { ...res, id: nodeId, memory_type: mtype, backend: backendMode, namespace, train: trainResult }
|
|
6784
|
+
content: [{ type: "text", text: `Stored [${mtype}] memory (id: ${nodeId}) \u2192 backend: ${backendMode}, namespace: ${namespace}, source: ${storeSource}${trainResult ? ` | SIU: ${trainResult}` : ""}` }],
|
|
6785
|
+
details: { ...res, id: nodeId, memory_type: mtype, backend: backendMode, namespace, source: storeSource, train: trainResult }
|
|
5221
6786
|
};
|
|
5222
6787
|
}
|
|
5223
6788
|
},
|
|
@@ -5229,9 +6794,14 @@ var toolDefinitions = {
|
|
|
5229
6794
|
parameters: Type.Object({})
|
|
5230
6795
|
},
|
|
5231
6796
|
options: { name: "memory_status" },
|
|
5232
|
-
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, storeLibPath, vectorsLibPath, wasmDir, isAvailable }) => async (_id, _params) => {
|
|
6797
|
+
makeExecute: ({ sulcusMem, localClient, retryQueue, backendMode, namespace, nativeLoader, storeLibPath, vectorsLibPath, wasmDir, isAvailable }) => async (_id, _params) => {
|
|
6798
|
+
const localStatus = localClient ? {
|
|
6799
|
+
endpoint: localClient.endpoint,
|
|
6800
|
+
...localClient.healthSummary(),
|
|
6801
|
+
retry_queue_size: retryQueue.size
|
|
6802
|
+
} : null;
|
|
5233
6803
|
if (!isAvailable || !sulcusMem) {
|
|
5234
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "unavailable", backend: backendMode, namespace, error: nativeLoader.error || "not loaded", storeLib: storeLibPath, vectorsLib: vectorsLibPath, wasmDir }, null, 2) }] };
|
|
6804
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "unavailable", backend: backendMode, namespace, error: nativeLoader.error || "not loaded", storeLib: storeLibPath, vectorsLib: vectorsLibPath, wasmDir, local: localStatus }, null, 2) }] };
|
|
5235
6805
|
}
|
|
5236
6806
|
try {
|
|
5237
6807
|
const [statusInfo, hotNodes] = await Promise.all([
|
|
@@ -5287,7 +6857,7 @@ var toolDefinitions = {
|
|
|
5287
6857
|
};
|
|
5288
6858
|
}
|
|
5289
6859
|
return {
|
|
5290
|
-
content: [{ type: "text", text: JSON.stringify({ status: "ok", backend: backendMode, namespace, ...si?.capabilities ? { capabilities: si.capabilities } : {}, ...si?.stats ? { stats: si.stats } : {}, hot_node_count: nodeList.length, hot_nodes: nodeList, recall_quality: recallQuality, last_injection: lastInjection }, null, 2) }],
|
|
6860
|
+
content: [{ type: "text", text: JSON.stringify({ status: "ok", backend: backendMode, namespace, ...si?.capabilities ? { capabilities: si.capabilities } : {}, ...si?.stats ? { stats: si.stats } : {}, hot_node_count: nodeList.length, hot_nodes: nodeList, recall_quality: recallQuality, last_injection: lastInjection, ...localStatus ? { local: localStatus } : {} }, null, 2) }],
|
|
5291
6861
|
details: { status: "ok", backend: backendMode, namespace, count: nodeList.length }
|
|
5292
6862
|
};
|
|
5293
6863
|
} catch (e) {
|
|
@@ -5365,13 +6935,32 @@ var toolDefinitions = {
|
|
|
5365
6935
|
})
|
|
5366
6936
|
},
|
|
5367
6937
|
options: { name: "memory_delete" },
|
|
5368
|
-
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, isAvailable }) => async (_id, params) => {
|
|
6938
|
+
makeExecute: ({ sulcusMem, localClient, retryQueue, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
5369
6939
|
if (!isAvailable || !sulcusMem) throw new Error(`Sulcus unavailable: ${nativeLoader.error || "not loaded"}`);
|
|
6940
|
+
const memId = params.id;
|
|
5370
6941
|
const train = params.train !== false;
|
|
5371
|
-
|
|
6942
|
+
if (localClient && localClient.isAvailable()) {
|
|
6943
|
+
try {
|
|
6944
|
+
await localClient.delete_memory(memId);
|
|
6945
|
+
logger.debug?.(`sulcus: deleted from local sidecar (${memId})`);
|
|
6946
|
+
} catch (localErr) {
|
|
6947
|
+
logger.debug?.(`sulcus: local delete failed (${localErr})`);
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
let res;
|
|
6951
|
+
try {
|
|
6952
|
+
res = await sulcusMem.delete_memory(memId, train);
|
|
6953
|
+
} catch (remoteErr) {
|
|
6954
|
+
retryQueue.enqueue(memId, "delete", { id: memId });
|
|
6955
|
+
logger.debug?.(`sulcus: remote delete failed, enqueued for retry (${memId})`);
|
|
6956
|
+
return {
|
|
6957
|
+
content: [{ type: "text", text: `Deleted memory ${memId} locally. Remote sync pending.` }],
|
|
6958
|
+
details: { id: memId, trained: train, backend: backendMode, namespace, source: "local" }
|
|
6959
|
+
};
|
|
6960
|
+
}
|
|
5372
6961
|
return {
|
|
5373
|
-
content: [{ type: "text", text: `Deleted memory ${
|
|
5374
|
-
details: { id:
|
|
6962
|
+
content: [{ type: "text", text: `Deleted memory ${memId}${train ? " (trained SIVU to reject similar)" : ""}. Backend: ${backendMode}, namespace: ${namespace}` }],
|
|
6963
|
+
details: { id: memId, trained: train, result: res, backend: backendMode, namespace }
|
|
5375
6964
|
};
|
|
5376
6965
|
}
|
|
5377
6966
|
},
|
|
@@ -5385,15 +6974,31 @@ var toolDefinitions = {
|
|
|
5385
6974
|
})
|
|
5386
6975
|
},
|
|
5387
6976
|
options: { name: "memory_get" },
|
|
5388
|
-
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, isAvailable }) => async (_id, params) => {
|
|
6977
|
+
makeExecute: ({ sulcusMem, localClient, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
5389
6978
|
if (!isAvailable || !sulcusMem) throw new Error(`Sulcus unavailable: ${nativeLoader.error || "not loaded"}`);
|
|
5390
6979
|
if (!(sulcusMem instanceof SulcusCloudClient)) throw new Error("memory_get requires cloud backend");
|
|
5391
6980
|
const memId = params.id;
|
|
6981
|
+
let source = "cloud";
|
|
6982
|
+
if (localClient && localClient.isAvailable()) {
|
|
6983
|
+
try {
|
|
6984
|
+
const localRes = await localClient.get_memory(memId);
|
|
6985
|
+
if (localRes) {
|
|
6986
|
+
source = "local";
|
|
6987
|
+
logger.debug?.(`sulcus: memory_get served from local (${memId})`);
|
|
6988
|
+
return {
|
|
6989
|
+
content: [{ type: "text", text: JSON.stringify(localRes, null, 2) }],
|
|
6990
|
+
details: { ...localRes, backend: backendMode, namespace, source }
|
|
6991
|
+
};
|
|
6992
|
+
}
|
|
6993
|
+
} catch {
|
|
6994
|
+
logger.debug?.(`sulcus: local get failed for ${memId}, falling back to cloud`);
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
5392
6997
|
const res = await sulcusMem.get_memory(memId);
|
|
5393
6998
|
if (!res) return { content: [{ type: "text", text: `Memory ${memId} not found.` }], details: { found: false, id: memId } };
|
|
5394
6999
|
return {
|
|
5395
7000
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
5396
|
-
details: { ...res, backend: backendMode, namespace }
|
|
7001
|
+
details: { ...res, backend: backendMode, namespace, source }
|
|
5397
7002
|
};
|
|
5398
7003
|
}
|
|
5399
7004
|
},
|
|
@@ -5466,7 +7071,7 @@ var toolDefinitions = {
|
|
|
5466
7071
|
})
|
|
5467
7072
|
},
|
|
5468
7073
|
options: { name: "memory_update" },
|
|
5469
|
-
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
7074
|
+
makeExecute: ({ sulcusMem, localClient, retryQueue, backendMode, namespace, nativeLoader, isAvailable, logger }) => async (_id, params) => {
|
|
5470
7075
|
if (!isAvailable || !sulcusMem) throw new Error(`Sulcus unavailable: ${nativeLoader.error || "not loaded"}`);
|
|
5471
7076
|
if (!(sulcusMem instanceof SulcusCloudClient)) throw new Error("memory_update requires cloud backend");
|
|
5472
7077
|
const memId = params.id;
|
|
@@ -5478,8 +7083,26 @@ var toolDefinitions = {
|
|
|
5478
7083
|
if (Object.keys(updates).length === 0) {
|
|
5479
7084
|
return { content: [{ type: "text", text: "No fields to update. Provide at least one of: content, memory_type, is_pinned, heat." }] };
|
|
5480
7085
|
}
|
|
5481
|
-
|
|
7086
|
+
if (localClient && localClient.isAvailable()) {
|
|
7087
|
+
try {
|
|
7088
|
+
await localClient.update_memory(memId, updates);
|
|
7089
|
+
logger.debug?.(`sulcus: updated local sidecar (${memId})`);
|
|
7090
|
+
} catch (localErr) {
|
|
7091
|
+
logger.debug?.(`sulcus: local update failed (${localErr})`);
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
let res;
|
|
5482
7095
|
const fields = Object.keys(updates).join(", ");
|
|
7096
|
+
try {
|
|
7097
|
+
res = await sulcusMem.update_memory(memId, updates);
|
|
7098
|
+
} catch (remoteErr) {
|
|
7099
|
+
retryQueue.enqueue(memId, "update", updates);
|
|
7100
|
+
logger.debug?.(`sulcus: remote update failed, enqueued for retry (${memId})`);
|
|
7101
|
+
return {
|
|
7102
|
+
content: [{ type: "text", text: `Updated memory ${memId} locally (fields: ${fields}). Remote sync pending.` }],
|
|
7103
|
+
details: { id: memId, updated_fields: Object.keys(updates), backend: backendMode, namespace, source: "local" }
|
|
7104
|
+
};
|
|
7105
|
+
}
|
|
5483
7106
|
logger.info(`sulcus: memory_update \u2014 updated ${memId} (fields: ${fields})`);
|
|
5484
7107
|
return {
|
|
5485
7108
|
content: [{ type: "text", text: `Updated memory ${memId} (fields: ${fields}). Backend: ${backendMode}, namespace: ${namespace}` }],
|
|
@@ -6508,6 +8131,11 @@ var sulcusPlugin = {
|
|
|
6508
8131
|
const wasmDir = pluginConfig?.wasmDir ? (0, import_node_path.resolve)(pluginConfig.wasmDir) : (0, import_node_path.resolve)(__dirname, "wasm");
|
|
6509
8132
|
const serverUrl = pluginConfig?.serverUrl;
|
|
6510
8133
|
const apiKey = pluginConfig?.apiKey;
|
|
8134
|
+
const localConfig = pluginConfig?.local;
|
|
8135
|
+
const localEndpoint = localConfig?.endpoint ?? process.env.SULCUS_LOCAL_URL;
|
|
8136
|
+
const localApiKey = localConfig?.apiKey ?? process.env.SULCUS_LOCAL_API_KEY;
|
|
8137
|
+
const localTimeoutMs = localConfig?.timeoutMs ?? 2e3;
|
|
8138
|
+
const localEnabled = localEndpoint !== void 0 && localConfig?.enabled !== false;
|
|
6511
8139
|
const agentId = pluginConfig?.agentId;
|
|
6512
8140
|
const namespace = pluginConfig?.namespace === "default" && agentId ? agentId : pluginConfig?.namespace || agentId || "default";
|
|
6513
8141
|
const autoRecall = pluginConfig?.autoRecall ?? false;
|
|
@@ -6558,10 +8186,33 @@ var sulcusPlugin = {
|
|
|
6558
8186
|
}
|
|
6559
8187
|
const isAvailable = sulcusMem !== null;
|
|
6560
8188
|
const isCloudBackend = backendMode === "cloud" && sulcusMem instanceof SulcusCloudClient;
|
|
6561
|
-
|
|
8189
|
+
let localClient = null;
|
|
8190
|
+
const retryQueue = new RetryQueue({ maxItems: 500, maxRetries: 5, logger });
|
|
8191
|
+
if (localEnabled && localEndpoint) {
|
|
8192
|
+
localClient = new SulcusLocalClient({
|
|
8193
|
+
endpoint: localEndpoint,
|
|
8194
|
+
apiKey: localApiKey,
|
|
8195
|
+
timeoutMs: localTimeoutMs,
|
|
8196
|
+
logger
|
|
8197
|
+
});
|
|
8198
|
+
logger.info(`sulcus: local sidecar client created (endpoint: ${localEndpoint}, timeout: ${localTimeoutMs}ms)`);
|
|
8199
|
+
localClient.probe().then((ok) => {
|
|
8200
|
+
if (ok) logger.info("sulcus: local sidecar probe OK \u2705");
|
|
8201
|
+
else logger.warn("sulcus: local sidecar probe failed \u2014 will retry on first use");
|
|
8202
|
+
}).catch(() => {
|
|
8203
|
+
logger.warn("sulcus: local sidecar probe failed \u2014 will retry on first use");
|
|
8204
|
+
});
|
|
8205
|
+
} else if (localConfig?.enabled === true && !localEndpoint) {
|
|
8206
|
+
logger.warn("sulcus: local.enabled=true but no local.endpoint or SULCUS_LOCAL_URL set \u2014 local-first disabled");
|
|
8207
|
+
}
|
|
8208
|
+
const hasLocalClient = localClient !== null;
|
|
8209
|
+
const effectiveBackendMode = hasLocalClient && isCloudBackend ? "dual" : backendMode;
|
|
8210
|
+
STATIC_AWARENESS = buildStaticAwareness(effectiveBackendMode, namespace);
|
|
6562
8211
|
REBUILD_TOKEN_BUDGET = contextRebuildBudget;
|
|
6563
8212
|
if (isAvailable) {
|
|
6564
|
-
|
|
8213
|
+
const localTag = hasLocalClient ? `, local: ${localEndpoint}` : "";
|
|
8214
|
+
const retryTag = hasLocalClient ? ", retryQueue: enabled" : "";
|
|
8215
|
+
logger.info(`sulcus: ready \u2705 (backend: ${effectiveBackendMode}, namespace: ${namespace}, autoRecall: ${autoRecall}, autoCapture: ${autoCapture}, captureFromAssistant: ${captureFromAssistant}, contextRebuild: ${contextRebuildEnabled}${localTag}${retryTag})`);
|
|
6565
8216
|
} else {
|
|
6566
8217
|
const hints = [];
|
|
6567
8218
|
if (!serverUrl && !apiKey) {
|
|
@@ -6582,7 +8233,9 @@ var sulcusPlugin = {
|
|
|
6582
8233
|
const siuRequestFn = isCloudBackend && sulcusMem ? (method, path, body) => sulcusMem.request(method, path, body) : null;
|
|
6583
8234
|
const toolDeps = {
|
|
6584
8235
|
sulcusMem,
|
|
6585
|
-
|
|
8236
|
+
localClient,
|
|
8237
|
+
retryQueue,
|
|
8238
|
+
backendMode: effectiveBackendMode,
|
|
6586
8239
|
namespace,
|
|
6587
8240
|
nativeLoader,
|
|
6588
8241
|
storeLibPath,
|
|
@@ -6626,7 +8279,9 @@ var sulcusPlugin = {
|
|
|
6626
8279
|
try {
|
|
6627
8280
|
api.registerMemoryFlushPlan(() => {
|
|
6628
8281
|
if (!isAvailable || !sulcusMem) return null;
|
|
8282
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6629
8283
|
return {
|
|
8284
|
+
relativePath: `memory/${today}.md`,
|
|
6630
8285
|
softThresholdTokens: 15e3,
|
|
6631
8286
|
forceFlushTranscriptBytes: "2mb",
|
|
6632
8287
|
reserveTokensFloor: 3e4,
|
|
@@ -6672,7 +8327,7 @@ var sulcusPlugin = {
|
|
|
6672
8327
|
const errorsHit = [];
|
|
6673
8328
|
const userIntents = [];
|
|
6674
8329
|
const assistantWork = [];
|
|
6675
|
-
const
|
|
8330
|
+
const DECISION_MARKERS2 = ["decided", "will use", "going to", "plan is", "the fix", "conclusion", "recommend", "approach"];
|
|
6676
8331
|
for (const msg of msgs) {
|
|
6677
8332
|
const role = msg.role ?? msg.type;
|
|
6678
8333
|
const text = typeof msg.content === "string" ? msg.content : typeof msg.text === "string" ? msg.text : Array.isArray(msg.content) ? msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n") : "";
|
|
@@ -6682,10 +8337,10 @@ var sulcusPlugin = {
|
|
|
6682
8337
|
}
|
|
6683
8338
|
if ((role === "assistant" || role === "ai") && text.length > 50) {
|
|
6684
8339
|
const lc = text.toLowerCase();
|
|
6685
|
-
if (
|
|
8340
|
+
if (DECISION_MARKERS2.some((m) => lc.includes(m))) {
|
|
6686
8341
|
const sentences = text.split(/[.!?\n]/).filter((s) => s.trim().length > 15);
|
|
6687
8342
|
for (const s of sentences) {
|
|
6688
|
-
if (
|
|
8343
|
+
if (DECISION_MARKERS2.some((m) => s.toLowerCase().includes(m))) {
|
|
6689
8344
|
decisions.push(s.trim().substring(0, 300));
|
|
6690
8345
|
if (decisions.length >= 8) break;
|
|
6691
8346
|
}
|
|
@@ -6833,6 +8488,46 @@ ${activity.join("\n")}`);
|
|
|
6833
8488
|
logger.warn("sulcus: registerService failed: " + (e instanceof Error ? e.message : String(e)));
|
|
6834
8489
|
}
|
|
6835
8490
|
}
|
|
8491
|
+
if (hasLocalClient && isCloudBackend && sulcusMem) {
|
|
8492
|
+
const retryApiOn = api.on;
|
|
8493
|
+
const retryCloudClient = sulcusMem;
|
|
8494
|
+
retryApiOn("before_prompt_build", async () => {
|
|
8495
|
+
if (retryQueue.size === 0) return;
|
|
8496
|
+
try {
|
|
8497
|
+
await retryQueue.flush(async (item) => {
|
|
8498
|
+
switch (item.operation) {
|
|
8499
|
+
case "store": {
|
|
8500
|
+
const p = item.payload;
|
|
8501
|
+
await retryCloudClient.add_memory(
|
|
8502
|
+
p.content,
|
|
8503
|
+
p.memory_type,
|
|
8504
|
+
p.extraction_hints
|
|
8505
|
+
);
|
|
8506
|
+
break;
|
|
8507
|
+
}
|
|
8508
|
+
case "update": {
|
|
8509
|
+
await retryCloudClient.update_memory(item.key, item.payload);
|
|
8510
|
+
break;
|
|
8511
|
+
}
|
|
8512
|
+
case "delete": {
|
|
8513
|
+
await retryCloudClient.delete_memory(item.key);
|
|
8514
|
+
break;
|
|
8515
|
+
}
|
|
8516
|
+
case "boost": {
|
|
8517
|
+
const boosts = item.payload.boosts;
|
|
8518
|
+
if (boosts) await retryCloudClient.boost_batch(boosts);
|
|
8519
|
+
break;
|
|
8520
|
+
}
|
|
8521
|
+
default:
|
|
8522
|
+
logger.warn(`sulcus-retry: unknown operation ${item.operation}`);
|
|
8523
|
+
}
|
|
8524
|
+
});
|
|
8525
|
+
} catch (flushErr) {
|
|
8526
|
+
logger.debug?.(`sulcus: retry queue flush error: ${flushErr}`);
|
|
8527
|
+
}
|
|
8528
|
+
});
|
|
8529
|
+
logger.info("sulcus: registered retry queue flush hook");
|
|
8530
|
+
}
|
|
6836
8531
|
if (isCloudBackend && sulcusMem) {
|
|
6837
8532
|
if (autoRecall) {
|
|
6838
8533
|
const sdkRecallHandler = buildSdkRecallHandler(
|
|
@@ -6844,7 +8539,8 @@ ${activity.join("\n")}`);
|
|
|
6844
8539
|
boostOnRecallEnabled,
|
|
6845
8540
|
tokenBudget,
|
|
6846
8541
|
contextRebuildEnabled,
|
|
6847
|
-
contextWindowSize
|
|
8542
|
+
contextWindowSize,
|
|
8543
|
+
hasLocalClient ? localClient : null
|
|
6848
8544
|
);
|
|
6849
8545
|
const apiOn = api.on;
|
|
6850
8546
|
apiOn("before_prompt_build", async (event, ctx) => {
|
|
@@ -6930,17 +8626,17 @@ ${activity.join("\n")}`);
|
|
|
6930
8626
|
const commandsRun = [];
|
|
6931
8627
|
const decisions = [];
|
|
6932
8628
|
const errors = [];
|
|
6933
|
-
const
|
|
8629
|
+
const DECISION_MARKERS2 = ["decided", "will use", "going to", "plan is", "the fix", "conclusion", "recommend", "approach"];
|
|
6934
8630
|
const ERROR_MARKERS = ["error:", "failed:", "exception", "traceback", "panicked", "stack trace"];
|
|
6935
8631
|
for (const msg of messages) {
|
|
6936
8632
|
const role = msg.role ?? msg.type;
|
|
6937
8633
|
const rawContent = typeof msg.content === "string" ? msg.content : typeof msg.text === "string" ? msg.text : "";
|
|
6938
8634
|
if ((role === "assistant" || role === "ai") && rawContent.length > 20) {
|
|
6939
8635
|
const lc = rawContent.toLowerCase();
|
|
6940
|
-
if (
|
|
8636
|
+
if (DECISION_MARKERS2.some((m) => lc.includes(m))) {
|
|
6941
8637
|
const sentences = rawContent.split(/[.!?\n]/).filter((s) => s.trim().length > 10);
|
|
6942
8638
|
for (const s of sentences) {
|
|
6943
|
-
if (
|
|
8639
|
+
if (DECISION_MARKERS2.some((m) => s.toLowerCase().includes(m)) && !decisions.includes(s.trim())) {
|
|
6944
8640
|
decisions.push(s.trim().substring(0, 200));
|
|
6945
8641
|
if (decisions.length >= 5) break;
|
|
6946
8642
|
}
|
|
@@ -7031,7 +8727,7 @@ ${activity.join("\n")}`);
|
|
|
7031
8727
|
const dreamMinMemories = pluginConfig?.dreamMinMemories ?? 50;
|
|
7032
8728
|
const dreamMinHeat = pluginConfig?.dreamConsolidateMinHeat ?? 0.1;
|
|
7033
8729
|
if (dreamEnabled && isAvailable && sulcusMem instanceof SulcusCloudClient) {
|
|
7034
|
-
let
|
|
8730
|
+
let readDreamState2 = function() {
|
|
7035
8731
|
try {
|
|
7036
8732
|
if ((0, import_node_fs.existsSync)(dreamStateFile)) {
|
|
7037
8733
|
const raw = (0, import_node_fs.readFileSync)(dreamStateFile, "utf-8");
|
|
@@ -7044,12 +8740,12 @@ ${activity.join("\n")}`);
|
|
|
7044
8740
|
} catch {
|
|
7045
8741
|
}
|
|
7046
8742
|
return { lastDreamMs: 0, lastSessionCount: 0 };
|
|
7047
|
-
},
|
|
8743
|
+
}, writeDreamState2 = function(state) {
|
|
7048
8744
|
try {
|
|
7049
8745
|
(0, import_node_fs.writeFileSync)(dreamStateFile, JSON.stringify(state));
|
|
7050
8746
|
} catch {
|
|
7051
8747
|
}
|
|
7052
|
-
},
|
|
8748
|
+
}, acquireDreamLock2 = function() {
|
|
7053
8749
|
try {
|
|
7054
8750
|
if ((0, import_node_fs.existsSync)(dreamLockFile)) {
|
|
7055
8751
|
const lockAge = Date.now() - (JSON.parse((0, import_node_fs.readFileSync)(dreamLockFile, "utf-8")).ts ?? 0);
|
|
@@ -7060,12 +8756,13 @@ ${activity.join("\n")}`);
|
|
|
7060
8756
|
} catch {
|
|
7061
8757
|
return false;
|
|
7062
8758
|
}
|
|
7063
|
-
},
|
|
8759
|
+
}, releaseDreamLock2 = function() {
|
|
7064
8760
|
try {
|
|
7065
8761
|
if ((0, import_node_fs.existsSync)(dreamLockFile)) require("node:fs").unlinkSync(dreamLockFile);
|
|
7066
8762
|
} catch {
|
|
7067
8763
|
}
|
|
7068
8764
|
};
|
|
8765
|
+
var readDreamState = readDreamState2, writeDreamState = writeDreamState2, acquireDreamLock = acquireDreamLock2, releaseDreamLock = releaseDreamLock2;
|
|
7069
8766
|
const stateDir = (0, import_node_path.resolve)(__dirname, ".sulcus-state");
|
|
7070
8767
|
if (!(0, import_node_fs.existsSync)(stateDir)) (0, import_node_fs.mkdirSync)(stateDir, { recursive: true });
|
|
7071
8768
|
const dreamStateFile = (0, import_node_path.resolve)(stateDir, "dream-state.json");
|
|
@@ -7079,7 +8776,7 @@ ${activity.join("\n")}`);
|
|
|
7079
8776
|
dreamApiOn("agent_end", async () => {
|
|
7080
8777
|
if (dreamSessionCount % dreamSessionInterval !== 0) return;
|
|
7081
8778
|
if (dreamSessionCount === 0) return;
|
|
7082
|
-
const state =
|
|
8779
|
+
const state = readDreamState2();
|
|
7083
8780
|
const elapsed = Date.now() - state.lastDreamMs;
|
|
7084
8781
|
if (elapsed < dreamMinGapMs) {
|
|
7085
8782
|
logger.info(`sulcus/dream: gate 2 skip \u2014 ${Math.round(elapsed / 36e5)}h since last dream (need ${Math.round(dreamMinGapMs / 36e5)}h)`);
|
|
@@ -7098,18 +8795,18 @@ ${activity.join("\n")}`);
|
|
|
7098
8795
|
logger.warn(`sulcus/dream: gate 3 error \u2014 ${e instanceof Error ? e.message : e}`);
|
|
7099
8796
|
return;
|
|
7100
8797
|
}
|
|
7101
|
-
if (!
|
|
8798
|
+
if (!acquireDreamLock2()) {
|
|
7102
8799
|
logger.info("sulcus/dream: lock held \u2014 another consolidation in progress");
|
|
7103
8800
|
return;
|
|
7104
8801
|
}
|
|
7105
8802
|
logger.info(`sulcus/dream: triggering consolidation (minHeat=${dreamMinHeat})`);
|
|
7106
8803
|
sulcusMem.consolidate(dreamMinHeat).then((result) => {
|
|
7107
|
-
|
|
8804
|
+
writeDreamState2({ lastDreamMs: Date.now(), lastSessionCount: dreamSessionCount });
|
|
7108
8805
|
logger.info(`sulcus/dream: consolidation complete \u2014 ${JSON.stringify(result)}`);
|
|
7109
8806
|
}).catch((e) => {
|
|
7110
8807
|
logger.warn(`sulcus/dream: consolidation failed \u2014 ${e instanceof Error ? e.message : e}`);
|
|
7111
8808
|
}).finally(() => {
|
|
7112
|
-
|
|
8809
|
+
releaseDreamLock2();
|
|
7113
8810
|
});
|
|
7114
8811
|
});
|
|
7115
8812
|
logger.info(`sulcus: dream auto-trigger enabled (every ${dreamSessionInterval} sessions, ${Math.round(dreamMinGapMs / 36e5)}h gap, min ${dreamMinMemories} memories)`);
|
|
@@ -7898,6 +9595,40 @@ ${res.items.length} shown${res.total ? ` of ${res.total}` : ""}`);
|
|
|
7898
9595
|
logger.warn(`sulcus: registerCommand failed: ${e instanceof Error ? e.message : e}`);
|
|
7899
9596
|
}
|
|
7900
9597
|
}
|
|
9598
|
+
const contextEngineEnabled = pluginConfig?.contextEngine?.enabled === true;
|
|
9599
|
+
const assemblyMode = pluginConfig?.contextEngine?.assemblyMode ?? "passthrough";
|
|
9600
|
+
const compactMode = pluginConfig?.contextEngine?.compactMode ?? "smart";
|
|
9601
|
+
if (contextEngineEnabled && typeof api.registerContextEngine === "function") {
|
|
9602
|
+
const ceVersion = "7.2.1";
|
|
9603
|
+
const ceThresholds = pluginConfig?.contextEngine?.thresholds ?? {};
|
|
9604
|
+
api.registerContextEngine("openclaw-sulcus", async () => {
|
|
9605
|
+
let delegateCompaction;
|
|
9606
|
+
try {
|
|
9607
|
+
const sdk = await import("openclaw/plugin-sdk");
|
|
9608
|
+
delegateCompaction = sdk.delegateCompactionToRuntime;
|
|
9609
|
+
} catch {
|
|
9610
|
+
const sdk = await import("@anthropic-ai/openclaw-plugin-sdk");
|
|
9611
|
+
delegateCompaction = sdk.delegateCompactionToRuntime;
|
|
9612
|
+
}
|
|
9613
|
+
const engine = new SulcusContextEngine({
|
|
9614
|
+
version: ceVersion,
|
|
9615
|
+
assemblyMode,
|
|
9616
|
+
compactMode,
|
|
9617
|
+
logger,
|
|
9618
|
+
delegateCompaction,
|
|
9619
|
+
memoryClient: sulcusMem && sulcusMem instanceof SulcusCloudClient ? sulcusMem : null,
|
|
9620
|
+
namespace,
|
|
9621
|
+
thresholds: ceThresholds
|
|
9622
|
+
});
|
|
9623
|
+
return engine;
|
|
9624
|
+
});
|
|
9625
|
+
contextEngineActive = true;
|
|
9626
|
+
logger.info(`sulcus: context engine registered (v7.2.1, ownsCompaction: true, assembly: ${assemblyMode})`);
|
|
9627
|
+
} else if (contextEngineEnabled) {
|
|
9628
|
+
logger.warn("sulcus: contextEngine.enabled=true but api.registerContextEngine not available \u2014 skipping");
|
|
9629
|
+
} else {
|
|
9630
|
+
logger.info("sulcus: context engine disabled (set contextEngine.enabled: true to opt in)");
|
|
9631
|
+
}
|
|
7901
9632
|
if (isAvailable && sulcusMem instanceof SulcusCloudClient) {
|
|
7902
9633
|
importOpenClawHistory(sulcusMem, logger).catch((e) => {
|
|
7903
9634
|
logger.warn(`sulcus: history import failed: ${e instanceof Error ? e.message : String(e)}`);
|