saico 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +287 -300
- package/index.js +1 -4
- package/itask.js +16 -3
- package/msgs.js +35 -81
- package/package.json +1 -2
- package/saico.js +307 -35
- package/sid.js +0 -248
package/index.js
CHANGED
|
@@ -12,15 +12,14 @@
|
|
|
12
12
|
* - Storage persistence (Redis cache + optional DB backend)
|
|
13
13
|
*
|
|
14
14
|
* Main Components:
|
|
15
|
+
* - Saico: Master class (external users extend this)
|
|
15
16
|
* - Itask: Base task class for all tasks (supports states, cancellation, promises)
|
|
16
17
|
* - Context: Conversation context with message handling and tool calls
|
|
17
|
-
* - Sid: Session root task (extends Itask, always has a context)
|
|
18
18
|
* - Store: Storage abstraction layer (Redis + optional backends like DynamoDB)
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const Itask = require('./itask.js');
|
|
22
22
|
const { Context, createContext } = require('./msgs.js');
|
|
23
|
-
const { Sid, createSid } = require('./sid.js');
|
|
24
23
|
const { Store, DynamoBackend } = require('./store.js');
|
|
25
24
|
const { Saico } = require('./saico.js');
|
|
26
25
|
const { DynamoDBAdapter } = require('./dynamo.js');
|
|
@@ -144,7 +143,6 @@ module.exports = {
|
|
|
144
143
|
// Core classes
|
|
145
144
|
Itask,
|
|
146
145
|
Context,
|
|
147
|
-
Sid,
|
|
148
146
|
Store,
|
|
149
147
|
DynamoBackend,
|
|
150
148
|
|
|
@@ -153,7 +151,6 @@ module.exports = {
|
|
|
153
151
|
|
|
154
152
|
// Factory functions
|
|
155
153
|
createTask,
|
|
156
|
-
createSid,
|
|
157
154
|
createContext,
|
|
158
155
|
|
|
159
156
|
// Legacy compatibility
|
package/itask.js
CHANGED
|
@@ -746,9 +746,22 @@ Itask.prototype.closeContext = async function closeContext(){
|
|
|
746
746
|
await this.context.close();
|
|
747
747
|
};
|
|
748
748
|
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
|
|
749
|
+
// Walk DOWN to find the deepest active descendant with a context
|
|
750
|
+
Itask.prototype.findDeepestContext = function findDeepestContext() {
|
|
751
|
+
let deepest = this.context ? { context: this.context, depth: 0 } : null;
|
|
752
|
+
const search = (task, depth) => {
|
|
753
|
+
for (const child of task.child) {
|
|
754
|
+
if (child._completed) continue;
|
|
755
|
+
if (child.context) {
|
|
756
|
+
if (!deepest || depth + 1 >= deepest.depth)
|
|
757
|
+
deepest = { context: child.context, depth: depth + 1 };
|
|
758
|
+
}
|
|
759
|
+
search(child, depth + 1);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
search(this, 0);
|
|
763
|
+
return deepest ? deepest.context : null;
|
|
764
|
+
};
|
|
752
765
|
|
|
753
766
|
// Reference to Context class (set by index.js to avoid circular dependency)
|
|
754
767
|
Itask.Context = null;
|
package/msgs.js
CHANGED
|
@@ -69,39 +69,6 @@ class Context {
|
|
|
69
69
|
this.functions = task?.functions;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Overridable: extending classes provide current state summary
|
|
73
|
-
getStateSummary() { return ''; }
|
|
74
|
-
|
|
75
|
-
// Recursively collect state summaries from child tasks that have no context
|
|
76
|
-
// (no msg Q), stopping at children that do have one.
|
|
77
|
-
_collectChildStateSummaries(task) {
|
|
78
|
-
if (!task.child || !task.child.size) return '';
|
|
79
|
-
const parts = [];
|
|
80
|
-
for (const child of task.child) {
|
|
81
|
-
if (child.context) continue; // has its own Q — boundary, stop here
|
|
82
|
-
if (typeof child.getStateSummary === 'function') {
|
|
83
|
-
const s = child.getStateSummary();
|
|
84
|
-
if (s) parts.push(s);
|
|
85
|
-
}
|
|
86
|
-
const nested = this._collectChildStateSummaries(child);
|
|
87
|
-
if (nested) parts.push(nested);
|
|
88
|
-
}
|
|
89
|
-
return parts.join('\n');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Internal (not overridable): own getStateSummary() + summaries from all
|
|
93
|
-
// contextless descendants, stopping at the first child that has its own Q.
|
|
94
|
-
_getStateSummary() {
|
|
95
|
-
const parts = [];
|
|
96
|
-
const own = this.getStateSummary();
|
|
97
|
-
if (own) parts.push(own);
|
|
98
|
-
if (this.task) {
|
|
99
|
-
const childSummaries = this._collectChildStateSummaries(this.task);
|
|
100
|
-
if (childSummaries) parts.push(childSummaries);
|
|
101
|
-
}
|
|
102
|
-
return parts.join('\n');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
72
|
// Snapshot all public (non-underscore) task properties for dirty detection.
|
|
106
73
|
// Mirrors the observable proxy convention: _ prefix = internal, ignored.
|
|
107
74
|
// Does NOT call serialize() — that is for persistence, not dirty detection.
|
|
@@ -822,52 +789,31 @@ class Context {
|
|
|
822
789
|
return msgs.slice(startIdx);
|
|
823
790
|
}
|
|
824
791
|
|
|
825
|
-
// Build message queue
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const prompt = {role: 'system', content: ctx.prompt};
|
|
838
|
-
if (add_tag) prompt.tag = ctx.tag;
|
|
792
|
+
// Build message queue.
|
|
793
|
+
// When preamble is provided (by Saico orchestrator), it is prepended as-is
|
|
794
|
+
// and does NOT count against QUEUE_LIMIT. Otherwise falls back to standalone
|
|
795
|
+
// behavior: own prompt + tool digest + own messages.
|
|
796
|
+
_createMsgQ(preamble, add_tag, tag_filter) {
|
|
797
|
+
const fullQueue = [...(preamble || [])];
|
|
798
|
+
|
|
799
|
+
// Standalone fallback — when no preamble provided, add own prompt/digest
|
|
800
|
+
if (!preamble) {
|
|
801
|
+
if (this.prompt) {
|
|
802
|
+
const prompt = {role: 'system', content: this.prompt};
|
|
803
|
+
if (add_tag) prompt.tag = this.tag;
|
|
839
804
|
fullQueue.push(prompt);
|
|
840
805
|
}
|
|
841
|
-
const ctxSummary = ctx._getStateSummary();
|
|
842
|
-
if (ctxSummary)
|
|
843
|
-
fullQueue.push({role: 'system', content: '[State Summary]\n' + ctxSummary});
|
|
844
|
-
}
|
|
845
|
-
if (this.prompt) {
|
|
846
|
-
const prompt = {role: 'system', content: this.prompt};
|
|
847
|
-
if (add_tag) prompt.tag = this.tag;
|
|
848
|
-
fullQueue.push(prompt);
|
|
849
|
-
}
|
|
850
|
-
const stateSummary = this._getStateSummary();
|
|
851
|
-
if (stateSummary)
|
|
852
|
-
fullQueue.push({role: 'system', content: '[State Summary]\n' + stateSummary});
|
|
853
|
-
|
|
854
|
-
// Layer 3: Tool digest (if non-empty)
|
|
855
|
-
if (this.tool_digest.length > 0) {
|
|
856
|
-
const digestText = this.tool_digest.map(entry =>
|
|
857
|
-
`[${new Date(entry.tm).toISOString()}] ${entry.tool}: ${entry.result}`
|
|
858
|
-
).join('\n');
|
|
859
|
-
fullQueue.push({role: 'system', content: '[Tool Activity Log]\n' + digestText});
|
|
860
|
-
}
|
|
861
806
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
.
|
|
866
|
-
.
|
|
867
|
-
|
|
807
|
+
if (this.tool_digest.length > 0) {
|
|
808
|
+
const digestText = this.tool_digest.map(entry =>
|
|
809
|
+
`[${new Date(entry.tm).toISOString()}] ${entry.tool}: ${entry.result}`
|
|
810
|
+
).join('\n');
|
|
811
|
+
fullQueue.push({role: 'system', content: '[Tool Activity Log]\n' + digestText});
|
|
812
|
+
}
|
|
868
813
|
}
|
|
869
814
|
|
|
870
815
|
// Own messages — filter by tag if requested, then slice to QUEUE_LIMIT
|
|
816
|
+
// QUEUE_LIMIT only applies here — preamble is not counted
|
|
871
817
|
let my_msgs;
|
|
872
818
|
if (tag_filter !== undefined) {
|
|
873
819
|
my_msgs = this._msgs.filter(m => {
|
|
@@ -948,7 +894,7 @@ class Context {
|
|
|
948
894
|
_debugQDump(Q, functions) {
|
|
949
895
|
if (util.is_mocha && process.env.PROD)
|
|
950
896
|
return;
|
|
951
|
-
const dbgQ = Q || this._createMsgQ(true);
|
|
897
|
+
const dbgQ = Q || this._createMsgQ(null, true);
|
|
952
898
|
if (debug) {
|
|
953
899
|
console.log('MSGQDEBUG - Q:', JSON.stringify(dbgQ.map(m => ({
|
|
954
900
|
role: m.role,
|
|
@@ -973,14 +919,22 @@ class Context {
|
|
|
973
919
|
this._processWaitingQueue();
|
|
974
920
|
}
|
|
975
921
|
|
|
976
|
-
Q = this._createMsgQ(false, o.opts?.tag);
|
|
922
|
+
Q = this._createMsgQ(o.opts?._preamble, false, o.opts?.tag);
|
|
977
923
|
|
|
978
|
-
//
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
924
|
+
// Use aggregated functions from Saico if provided, else fall back to own
|
|
925
|
+
let funcs;
|
|
926
|
+
if (o.opts?._aggregatedFunctions) {
|
|
927
|
+
const messageFuncs = o.functions || [];
|
|
928
|
+
funcs = [...o.opts._aggregatedFunctions, ...messageFuncs].length > 0
|
|
929
|
+
? [...o.opts._aggregatedFunctions, ...messageFuncs]
|
|
930
|
+
: null;
|
|
931
|
+
} else {
|
|
932
|
+
const hierarchyFuncs = this.getFunctions() || [];
|
|
933
|
+
const messageFuncs = o.functions || [];
|
|
934
|
+
funcs = [...hierarchyFuncs, ...messageFuncs].length > 0
|
|
935
|
+
? [...hierarchyFuncs, ...messageFuncs]
|
|
936
|
+
: null;
|
|
937
|
+
}
|
|
984
938
|
|
|
985
939
|
if (debug)
|
|
986
940
|
this._debugQDump(Q, funcs);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saico",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"description": "Hierarchical AI Conversation Orchestrator - Task hierarchy with conversation contexts",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"itask.js",
|
|
18
18
|
"context.js",
|
|
19
19
|
"msgs.js",
|
|
20
|
-
"sid.js",
|
|
21
20
|
"saico.js",
|
|
22
21
|
"dynamo.js",
|
|
23
22
|
"openai.js",
|