@caupulican/pi-adaptative 0.80.60 → 0.80.61
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/CHANGELOG.md +6 -0
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +32 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/memory/providers/transcript-recall.d.ts +27 -0
- package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -0
- package/dist/core/memory/providers/transcript-recall.js +154 -0
- package/dist/core/memory/providers/transcript-recall.js.map +1 -0
- package/dist/core/memory/transcript-index.d.ts +22 -0
- package/dist/core/memory/transcript-index.d.ts.map +1 -0
- package/dist/core/memory/transcript-index.js +85 -0
- package/dist/core/memory/transcript-index.js.map +1 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).
|
|
3
|
+
*
|
|
4
|
+
* A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL
|
|
5
|
+
* corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's
|
|
6
|
+
* tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most
|
|
7
|
+
* relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —
|
|
8
|
+
* the file-store remains the write target; this is the recall corpus.
|
|
9
|
+
*/
|
|
10
|
+
import type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from "../memory-provider.ts";
|
|
11
|
+
export declare class TranscriptRecallProvider implements MemoryProvider {
|
|
12
|
+
readonly name = "transcript-recall";
|
|
13
|
+
private index;
|
|
14
|
+
private currentSessionId;
|
|
15
|
+
private cwd;
|
|
16
|
+
private agentDir;
|
|
17
|
+
isAvailable(): boolean;
|
|
18
|
+
getCapabilities(): MemoryCapabilities;
|
|
19
|
+
initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void>;
|
|
20
|
+
shutdown(): Promise<void>;
|
|
21
|
+
/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */
|
|
22
|
+
getContextMarkers(): string[];
|
|
23
|
+
prefetch(query: string): Promise<string>;
|
|
24
|
+
private ensureIndex;
|
|
25
|
+
private buildDocs;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=transcript-recall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-recall.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAUxG,qBAAa,wBAAyB,YAAW,cAAc;IAC9D,QAAQ,CAAC,IAAI,uBAAuB;IACpC,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,QAAQ,CAAM;IAEtB,WAAW,IAAI,OAAO,CAErB;IAED,eAAe,IAAI,kBAAkB,CAEpC;IAEK,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAE9B;IAED,wFAAwF;IACxF,iBAAiB,IAAI,MAAM,EAAE,CAE5B;IAEK,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB7C;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,SAAS;CA+CjB","sourcesContent":["/**\n * TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).\n *\n * A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL\n * corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's\n * tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most\n * relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —\n * the file-store remains the write target; this is the recall corpus.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype FileEntry,\n\tgetDefaultSessionDir,\n\tisAutoLearnSessionId,\n\tloadEntriesFromFile,\n} from \"../../session-manager.ts\";\nimport type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\nimport { type TranscriptDoc, TranscriptIndex } from \"../transcript-index.ts\";\n\n/** Most-recent past sessions to consider. */\nconst MAX_SESSIONS = 60;\n/** Per-session text cap (keeps the index light and snippets relevant). */\nconst MAX_DOC_CHARS = 8_000;\n/** Overall corpus cap across all docs. */\nconst MAX_TOTAL_CHARS = 500_000;\n\nexport class TranscriptRecallProvider implements MemoryProvider {\n\treadonly name = \"transcript-recall\";\n\tprivate index: TranscriptIndex | undefined;\n\tprivate currentSessionId = \"\";\n\tprivate cwd = \"\";\n\tprivate agentDir = \"\";\n\n\tisAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): MemoryCapabilities {\n\t\treturn { surfaces: [\"context\"] };\n\t}\n\n\tasync initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.currentSessionId = sessionId;\n\t\tthis.cwd = ctx.cwd;\n\t\tthis.agentDir = ctx.agentDir;\n\t\tthis.index = undefined; // built lazily on first prefetch\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tthis.index = undefined;\n\t}\n\n\t/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */\n\tgetContextMarkers(): string[] {\n\t\treturn [\"<memory_context\"];\n\t}\n\n\tasync prefetch(query: string): Promise<string> {\n\t\tif (!query.trim()) return \"\";\n\t\tlet index: TranscriptIndex;\n\t\ttry {\n\t\t\tindex = this.ensureIndex();\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (index.size === 0) return \"\";\n\t\t// minScore is a query-CONTAINMENT threshold (fraction of the query's tokens present in the doc),\n\t\t// not Jaccard — so it is length-independent and recalls relevant long sessions. ~1/3 of query\n\t\t// terms must appear before a session is considered relevant.\n\t\tconst hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });\n\t\tif (hits.length === 0) return \"\";\n\t\tconst body = hits.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${h.snippet}`).join(\"\\n\");\n\t\treturn `<memory_context source=\"transcript-recall\">\\nRelevant context recalled from past sessions (read-only reference, may be stale):\\n${body}\\n</memory_context>`;\n\t}\n\n\tprivate ensureIndex(): TranscriptIndex {\n\t\tif (!this.index) {\n\t\t\tthis.index = new TranscriptIndex(this.buildDocs());\n\t\t}\n\t\treturn this.index;\n\t}\n\n\tprivate buildDocs(): TranscriptDoc[] {\n\t\tconst docs: TranscriptDoc[] = [];\n\t\tlet dir: string;\n\t\ttry {\n\t\t\tdir = getDefaultSessionDir(this.cwd, this.agentDir);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet files: Array<{ path: string; mtime: number }>;\n\t\ttry {\n\t\t\tfiles = readdirSync(dir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => {\n\t\t\t\t\tconst path = join(dir, f);\n\t\t\t\t\tlet mtime = 0;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmtime = statSync(path).mtimeMs;\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { path, mtime };\n\t\t\t\t})\n\t\t\t\t.sort((a, b) => b.mtime - a.mtime) // most-recent first\n\t\t\t\t.slice(0, MAX_SESSIONS);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet total = 0;\n\t\tfor (const { path } of files) {\n\t\t\tlet entries: FileEntry[];\n\t\t\ttry {\n\t\t\t\tentries = loadEntriesFromFile(path);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst header = entries.find((e): e is Extract<FileEntry, { type: \"session\" }> => e.type === \"session\");\n\t\t\tconst sessionId = header?.id;\n\t\t\tif (!sessionId || sessionId === this.currentSessionId || isAutoLearnSessionId(sessionId)) continue;\n\n\t\t\tconst text = extractSessionText(entries, MAX_DOC_CHARS);\n\t\t\tif (!text.trim()) continue;\n\t\t\tdocs.push({ sessionId, timestamp: header?.timestamp, text });\n\t\t\ttotal += text.length;\n\t\t\tif (total >= MAX_TOTAL_CHARS) break;\n\t\t}\n\t\treturn docs;\n\t}\n}\n\n/** Concatenate user+assistant text from a session's entries, capped to `maxChars`. */\nfunction extractSessionText(entries: FileEntry[], maxChars: number): string {\n\tconst parts: string[] = [];\n\tlet len = 0;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\t\tconst content = message.content;\n\t\tlet text = \"\";\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else if (Array.isArray(content)) {\n\t\t\ttext = content\n\t\t\t\t.map((b) => (b && typeof b === \"object\" && \"type\" in b && b.type === \"text\" ? (b.text ?? \"\") : \"\"))\n\t\t\t\t.join(\" \");\n\t\t}\n\t\ttext = text.trim();\n\t\tif (!text) continue;\n\t\tparts.push(text);\n\t\tlen += text.length;\n\t\tif (len >= maxChars) break;\n\t}\n\treturn parts.join(\"\\n\").slice(0, maxChars);\n}\n"]}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).
|
|
3
|
+
*
|
|
4
|
+
* A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL
|
|
5
|
+
* corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's
|
|
6
|
+
* tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most
|
|
7
|
+
* relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —
|
|
8
|
+
* the file-store remains the write target; this is the recall corpus.
|
|
9
|
+
*/
|
|
10
|
+
import { readdirSync, statSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { getDefaultSessionDir, isAutoLearnSessionId, loadEntriesFromFile, } from "../../session-manager.js";
|
|
13
|
+
import { TranscriptIndex } from "../transcript-index.js";
|
|
14
|
+
/** Most-recent past sessions to consider. */
|
|
15
|
+
const MAX_SESSIONS = 60;
|
|
16
|
+
/** Per-session text cap (keeps the index light and snippets relevant). */
|
|
17
|
+
const MAX_DOC_CHARS = 8_000;
|
|
18
|
+
/** Overall corpus cap across all docs. */
|
|
19
|
+
const MAX_TOTAL_CHARS = 500_000;
|
|
20
|
+
export class TranscriptRecallProvider {
|
|
21
|
+
name = "transcript-recall";
|
|
22
|
+
index;
|
|
23
|
+
currentSessionId = "";
|
|
24
|
+
cwd = "";
|
|
25
|
+
agentDir = "";
|
|
26
|
+
isAvailable() {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
getCapabilities() {
|
|
30
|
+
return { surfaces: ["context"] };
|
|
31
|
+
}
|
|
32
|
+
async initialize(sessionId, ctx) {
|
|
33
|
+
this.currentSessionId = sessionId;
|
|
34
|
+
this.cwd = ctx.cwd;
|
|
35
|
+
this.agentDir = ctx.agentDir;
|
|
36
|
+
this.index = undefined; // built lazily on first prefetch
|
|
37
|
+
}
|
|
38
|
+
async shutdown() {
|
|
39
|
+
this.index = undefined;
|
|
40
|
+
}
|
|
41
|
+
/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */
|
|
42
|
+
getContextMarkers() {
|
|
43
|
+
return ["<memory_context"];
|
|
44
|
+
}
|
|
45
|
+
async prefetch(query) {
|
|
46
|
+
if (!query.trim())
|
|
47
|
+
return "";
|
|
48
|
+
let index;
|
|
49
|
+
try {
|
|
50
|
+
index = this.ensureIndex();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
if (index.size === 0)
|
|
56
|
+
return "";
|
|
57
|
+
// minScore is a query-CONTAINMENT threshold (fraction of the query's tokens present in the doc),
|
|
58
|
+
// not Jaccard — so it is length-independent and recalls relevant long sessions. ~1/3 of query
|
|
59
|
+
// terms must appear before a session is considered relevant.
|
|
60
|
+
const hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });
|
|
61
|
+
if (hits.length === 0)
|
|
62
|
+
return "";
|
|
63
|
+
const body = hits.map((h) => `- (${h.timestamp ?? "earlier session"}) ${h.snippet}`).join("\n");
|
|
64
|
+
return `<memory_context source="transcript-recall">\nRelevant context recalled from past sessions (read-only reference, may be stale):\n${body}\n</memory_context>`;
|
|
65
|
+
}
|
|
66
|
+
ensureIndex() {
|
|
67
|
+
if (!this.index) {
|
|
68
|
+
this.index = new TranscriptIndex(this.buildDocs());
|
|
69
|
+
}
|
|
70
|
+
return this.index;
|
|
71
|
+
}
|
|
72
|
+
buildDocs() {
|
|
73
|
+
const docs = [];
|
|
74
|
+
let dir;
|
|
75
|
+
try {
|
|
76
|
+
dir = getDefaultSessionDir(this.cwd, this.agentDir);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return docs;
|
|
80
|
+
}
|
|
81
|
+
let files;
|
|
82
|
+
try {
|
|
83
|
+
files = readdirSync(dir)
|
|
84
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
85
|
+
.map((f) => {
|
|
86
|
+
const path = join(dir, f);
|
|
87
|
+
let mtime = 0;
|
|
88
|
+
try {
|
|
89
|
+
mtime = statSync(path).mtimeMs;
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
return { path, mtime };
|
|
93
|
+
})
|
|
94
|
+
.sort((a, b) => b.mtime - a.mtime) // most-recent first
|
|
95
|
+
.slice(0, MAX_SESSIONS);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return docs;
|
|
99
|
+
}
|
|
100
|
+
let total = 0;
|
|
101
|
+
for (const { path } of files) {
|
|
102
|
+
let entries;
|
|
103
|
+
try {
|
|
104
|
+
entries = loadEntriesFromFile(path);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const header = entries.find((e) => e.type === "session");
|
|
110
|
+
const sessionId = header?.id;
|
|
111
|
+
if (!sessionId || sessionId === this.currentSessionId || isAutoLearnSessionId(sessionId))
|
|
112
|
+
continue;
|
|
113
|
+
const text = extractSessionText(entries, MAX_DOC_CHARS);
|
|
114
|
+
if (!text.trim())
|
|
115
|
+
continue;
|
|
116
|
+
docs.push({ sessionId, timestamp: header?.timestamp, text });
|
|
117
|
+
total += text.length;
|
|
118
|
+
if (total >= MAX_TOTAL_CHARS)
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
return docs;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Concatenate user+assistant text from a session's entries, capped to `maxChars`. */
|
|
125
|
+
function extractSessionText(entries, maxChars) {
|
|
126
|
+
const parts = [];
|
|
127
|
+
let len = 0;
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (entry.type !== "message")
|
|
130
|
+
continue;
|
|
131
|
+
const message = entry.message;
|
|
132
|
+
if (message.role !== "user" && message.role !== "assistant")
|
|
133
|
+
continue;
|
|
134
|
+
const content = message.content;
|
|
135
|
+
let text = "";
|
|
136
|
+
if (typeof content === "string") {
|
|
137
|
+
text = content;
|
|
138
|
+
}
|
|
139
|
+
else if (Array.isArray(content)) {
|
|
140
|
+
text = content
|
|
141
|
+
.map((b) => (b && typeof b === "object" && "type" in b && b.type === "text" ? (b.text ?? "") : ""))
|
|
142
|
+
.join(" ");
|
|
143
|
+
}
|
|
144
|
+
text = text.trim();
|
|
145
|
+
if (!text)
|
|
146
|
+
continue;
|
|
147
|
+
parts.push(text);
|
|
148
|
+
len += text.length;
|
|
149
|
+
if (len >= maxChars)
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
return parts.join("\n").slice(0, maxChars);
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=transcript-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-recall.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).\n *\n * A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL\n * corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's\n * tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most\n * relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —\n * the file-store remains the write target; this is the recall corpus.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype FileEntry,\n\tgetDefaultSessionDir,\n\tisAutoLearnSessionId,\n\tloadEntriesFromFile,\n} from \"../../session-manager.ts\";\nimport type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\nimport { type TranscriptDoc, TranscriptIndex } from \"../transcript-index.ts\";\n\n/** Most-recent past sessions to consider. */\nconst MAX_SESSIONS = 60;\n/** Per-session text cap (keeps the index light and snippets relevant). */\nconst MAX_DOC_CHARS = 8_000;\n/** Overall corpus cap across all docs. */\nconst MAX_TOTAL_CHARS = 500_000;\n\nexport class TranscriptRecallProvider implements MemoryProvider {\n\treadonly name = \"transcript-recall\";\n\tprivate index: TranscriptIndex | undefined;\n\tprivate currentSessionId = \"\";\n\tprivate cwd = \"\";\n\tprivate agentDir = \"\";\n\n\tisAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): MemoryCapabilities {\n\t\treturn { surfaces: [\"context\"] };\n\t}\n\n\tasync initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.currentSessionId = sessionId;\n\t\tthis.cwd = ctx.cwd;\n\t\tthis.agentDir = ctx.agentDir;\n\t\tthis.index = undefined; // built lazily on first prefetch\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tthis.index = undefined;\n\t}\n\n\t/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */\n\tgetContextMarkers(): string[] {\n\t\treturn [\"<memory_context\"];\n\t}\n\n\tasync prefetch(query: string): Promise<string> {\n\t\tif (!query.trim()) return \"\";\n\t\tlet index: TranscriptIndex;\n\t\ttry {\n\t\t\tindex = this.ensureIndex();\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (index.size === 0) return \"\";\n\t\t// minScore is a query-CONTAINMENT threshold (fraction of the query's tokens present in the doc),\n\t\t// not Jaccard — so it is length-independent and recalls relevant long sessions. ~1/3 of query\n\t\t// terms must appear before a session is considered relevant.\n\t\tconst hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });\n\t\tif (hits.length === 0) return \"\";\n\t\tconst body = hits.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${h.snippet}`).join(\"\\n\");\n\t\treturn `<memory_context source=\"transcript-recall\">\\nRelevant context recalled from past sessions (read-only reference, may be stale):\\n${body}\\n</memory_context>`;\n\t}\n\n\tprivate ensureIndex(): TranscriptIndex {\n\t\tif (!this.index) {\n\t\t\tthis.index = new TranscriptIndex(this.buildDocs());\n\t\t}\n\t\treturn this.index;\n\t}\n\n\tprivate buildDocs(): TranscriptDoc[] {\n\t\tconst docs: TranscriptDoc[] = [];\n\t\tlet dir: string;\n\t\ttry {\n\t\t\tdir = getDefaultSessionDir(this.cwd, this.agentDir);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet files: Array<{ path: string; mtime: number }>;\n\t\ttry {\n\t\t\tfiles = readdirSync(dir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => {\n\t\t\t\t\tconst path = join(dir, f);\n\t\t\t\t\tlet mtime = 0;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmtime = statSync(path).mtimeMs;\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { path, mtime };\n\t\t\t\t})\n\t\t\t\t.sort((a, b) => b.mtime - a.mtime) // most-recent first\n\t\t\t\t.slice(0, MAX_SESSIONS);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet total = 0;\n\t\tfor (const { path } of files) {\n\t\t\tlet entries: FileEntry[];\n\t\t\ttry {\n\t\t\t\tentries = loadEntriesFromFile(path);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst header = entries.find((e): e is Extract<FileEntry, { type: \"session\" }> => e.type === \"session\");\n\t\t\tconst sessionId = header?.id;\n\t\t\tif (!sessionId || sessionId === this.currentSessionId || isAutoLearnSessionId(sessionId)) continue;\n\n\t\t\tconst text = extractSessionText(entries, MAX_DOC_CHARS);\n\t\t\tif (!text.trim()) continue;\n\t\t\tdocs.push({ sessionId, timestamp: header?.timestamp, text });\n\t\t\ttotal += text.length;\n\t\t\tif (total >= MAX_TOTAL_CHARS) break;\n\t\t}\n\t\treturn docs;\n\t}\n}\n\n/** Concatenate user+assistant text from a session's entries, capped to `maxChars`. */\nfunction extractSessionText(entries: FileEntry[], maxChars: number): string {\n\tconst parts: string[] = [];\n\tlet len = 0;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\t\tconst content = message.content;\n\t\tlet text = \"\";\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else if (Array.isArray(content)) {\n\t\t\ttext = content\n\t\t\t\t.map((b) => (b && typeof b === \"object\" && \"type\" in b && b.type === \"text\" ? (b.text ?? \"\") : \"\"))\n\t\t\t\t.join(\" \");\n\t\t}\n\t\ttext = text.trim();\n\t\tif (!text) continue;\n\t\tparts.push(text);\n\t\tlen += text.length;\n\t\tif (len >= maxChars) break;\n\t}\n\treturn parts.join(\"\\n\").slice(0, maxChars);\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface RecallHit {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
score: number;
|
|
4
|
+
snippet: string;
|
|
5
|
+
timestamp?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TranscriptDoc {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
timestamp?: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class TranscriptIndex {
|
|
13
|
+
private indexedDocs;
|
|
14
|
+
constructor(docs: TranscriptDoc[]);
|
|
15
|
+
query(queryText: string, opts?: {
|
|
16
|
+
k?: number;
|
|
17
|
+
minScore?: number;
|
|
18
|
+
maxSnippetChars?: number;
|
|
19
|
+
}): RecallHit[];
|
|
20
|
+
get size(): number;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=transcript-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-index.d.ts","sourceRoot":"","sources":["../../../src/core/memory/transcript-index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb;AAOD,qBAAa,eAAe;IAC3B,OAAO,CAAC,WAAW,CAAoB;IAEvC,YAAY,IAAI,EAAE,aAAa,EAAE,EAKhC;IAED,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,EAAE,CAmFxG;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;CACD","sourcesContent":["import { tokenize } from \"../tools/skill-audit.ts\";\n\nexport interface RecallHit {\n\tsessionId: string;\n\tscore: number;\n\tsnippet: string;\n\ttimestamp?: string;\n}\n\nexport interface TranscriptDoc {\n\tsessionId: string;\n\ttimestamp?: string;\n\ttext: string;\n}\n\ninterface IndexedDoc {\n\tdoc: TranscriptDoc;\n\ttokens: string[];\n}\n\nexport class TranscriptIndex {\n\tprivate indexedDocs: IndexedDoc[] = [];\n\n\tconstructor(docs: TranscriptDoc[]) {\n\t\tthis.indexedDocs = docs.map((doc) => ({\n\t\t\tdoc,\n\t\t\ttokens: tokenize(doc.text),\n\t\t}));\n\t}\n\n\tquery(queryText: string, opts?: { k?: number; minScore?: number; maxSnippetChars?: number }): RecallHit[] {\n\t\tconst k = opts?.k ?? 5;\n\t\tconst minScore = opts?.minScore ?? 0.34;\n\t\tconst maxSnippetChars = opts?.maxSnippetChars ?? 600;\n\n\t\tconst qTokens = tokenize(queryText);\n\t\tif (qTokens.length === 0 || this.indexedDocs.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst qSet = new Set(qTokens);\n\t\tconst hits: RecallHit[] = [];\n\n\t\tfor (const { doc, tokens } of this.indexedDocs) {\n\t\t\tconst dSet = new Set(tokens);\n\t\t\tlet intersection = 0;\n\t\t\tfor (const token of qSet) {\n\t\t\t\tif (dSet.has(token)) {\n\t\t\t\t\tintersection++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst score = intersection / qSet.size;\n\t\t\tif (score <= minScore) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Find matching indices of query tokens in the document text (case-insensitive)\n\t\t\tconst lowerText = doc.text.toLowerCase();\n\t\t\tconst matchIndices: number[] = [];\n\t\t\tfor (const token of qTokens) {\n\t\t\t\tlet pos = lowerText.indexOf(token);\n\t\t\t\twhile (pos !== -1) {\n\t\t\t\t\tmatchIndices.push(pos);\n\t\t\t\t\tpos = lowerText.indexOf(token, pos + 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet bestStart = 0;\n\t\t\tlet bestEnd = Math.min(doc.text.length, maxSnippetChars);\n\n\t\t\tif (matchIndices.length > 0) {\n\t\t\t\tmatchIndices.sort((a, b) => a - b);\n\t\t\t\tlet maxMatchesInWindow = 0;\n\n\t\t\t\tfor (const center of matchIndices) {\n\t\t\t\t\tlet start = Math.max(0, center - Math.floor(maxSnippetChars / 2));\n\t\t\t\t\tconst end = Math.min(doc.text.length, start + maxSnippetChars);\n\t\t\t\t\tif (end - start < maxSnippetChars && start > 0) {\n\t\t\t\t\t\tstart = Math.max(0, end - maxSnippetChars);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet matches = 0;\n\t\t\t\t\tfor (const idx of matchIndices) {\n\t\t\t\t\t\tif (idx >= start && idx < end) {\n\t\t\t\t\t\t\tmatches++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (matches > maxMatchesInWindow) {\n\t\t\t\t\t\tmaxMatchesInWindow = matches;\n\t\t\t\t\t\tbestStart = start;\n\t\t\t\t\t\tbestEnd = end;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rawSnippet = doc.text.slice(bestStart, bestEnd);\n\t\t\tconst prefix = bestStart > 0 ? \"...\" : \"\";\n\t\t\tconst suffix = bestEnd < doc.text.length ? \"...\" : \"\";\n\t\t\tconst snippet = prefix + rawSnippet + suffix;\n\n\t\t\thits.push({\n\t\t\t\tsessionId: doc.sessionId,\n\t\t\t\tscore,\n\t\t\t\tsnippet,\n\t\t\t\ttimestamp: doc.timestamp,\n\t\t\t});\n\t\t}\n\n\t\t// Sort by score desc\n\t\thits.sort((a, b) => b.score - a.score);\n\n\t\treturn hits.slice(0, k);\n\t}\n\n\tget size(): number {\n\t\treturn this.indexedDocs.length;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { tokenize } from "../tools/skill-audit.js";
|
|
2
|
+
export class TranscriptIndex {
|
|
3
|
+
indexedDocs = [];
|
|
4
|
+
constructor(docs) {
|
|
5
|
+
this.indexedDocs = docs.map((doc) => ({
|
|
6
|
+
doc,
|
|
7
|
+
tokens: tokenize(doc.text),
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
query(queryText, opts) {
|
|
11
|
+
const k = opts?.k ?? 5;
|
|
12
|
+
const minScore = opts?.minScore ?? 0.34;
|
|
13
|
+
const maxSnippetChars = opts?.maxSnippetChars ?? 600;
|
|
14
|
+
const qTokens = tokenize(queryText);
|
|
15
|
+
if (qTokens.length === 0 || this.indexedDocs.length === 0) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const qSet = new Set(qTokens);
|
|
19
|
+
const hits = [];
|
|
20
|
+
for (const { doc, tokens } of this.indexedDocs) {
|
|
21
|
+
const dSet = new Set(tokens);
|
|
22
|
+
let intersection = 0;
|
|
23
|
+
for (const token of qSet) {
|
|
24
|
+
if (dSet.has(token)) {
|
|
25
|
+
intersection++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const score = intersection / qSet.size;
|
|
29
|
+
if (score <= minScore) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Find matching indices of query tokens in the document text (case-insensitive)
|
|
33
|
+
const lowerText = doc.text.toLowerCase();
|
|
34
|
+
const matchIndices = [];
|
|
35
|
+
for (const token of qTokens) {
|
|
36
|
+
let pos = lowerText.indexOf(token);
|
|
37
|
+
while (pos !== -1) {
|
|
38
|
+
matchIndices.push(pos);
|
|
39
|
+
pos = lowerText.indexOf(token, pos + 1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let bestStart = 0;
|
|
43
|
+
let bestEnd = Math.min(doc.text.length, maxSnippetChars);
|
|
44
|
+
if (matchIndices.length > 0) {
|
|
45
|
+
matchIndices.sort((a, b) => a - b);
|
|
46
|
+
let maxMatchesInWindow = 0;
|
|
47
|
+
for (const center of matchIndices) {
|
|
48
|
+
let start = Math.max(0, center - Math.floor(maxSnippetChars / 2));
|
|
49
|
+
const end = Math.min(doc.text.length, start + maxSnippetChars);
|
|
50
|
+
if (end - start < maxSnippetChars && start > 0) {
|
|
51
|
+
start = Math.max(0, end - maxSnippetChars);
|
|
52
|
+
}
|
|
53
|
+
let matches = 0;
|
|
54
|
+
for (const idx of matchIndices) {
|
|
55
|
+
if (idx >= start && idx < end) {
|
|
56
|
+
matches++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (matches > maxMatchesInWindow) {
|
|
60
|
+
maxMatchesInWindow = matches;
|
|
61
|
+
bestStart = start;
|
|
62
|
+
bestEnd = end;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const rawSnippet = doc.text.slice(bestStart, bestEnd);
|
|
67
|
+
const prefix = bestStart > 0 ? "..." : "";
|
|
68
|
+
const suffix = bestEnd < doc.text.length ? "..." : "";
|
|
69
|
+
const snippet = prefix + rawSnippet + suffix;
|
|
70
|
+
hits.push({
|
|
71
|
+
sessionId: doc.sessionId,
|
|
72
|
+
score,
|
|
73
|
+
snippet,
|
|
74
|
+
timestamp: doc.timestamp,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Sort by score desc
|
|
78
|
+
hits.sort((a, b) => b.score - a.score);
|
|
79
|
+
return hits.slice(0, k);
|
|
80
|
+
}
|
|
81
|
+
get size() {
|
|
82
|
+
return this.indexedDocs.length;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=transcript-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-index.js","sourceRoot":"","sources":["../../../src/core/memory/transcript-index.ts"],"names":[],"mappings":"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","sourcesContent":["import { tokenize } from \"../tools/skill-audit.ts\";\n\nexport interface RecallHit {\n\tsessionId: string;\n\tscore: number;\n\tsnippet: string;\n\ttimestamp?: string;\n}\n\nexport interface TranscriptDoc {\n\tsessionId: string;\n\ttimestamp?: string;\n\ttext: string;\n}\n\ninterface IndexedDoc {\n\tdoc: TranscriptDoc;\n\ttokens: string[];\n}\n\nexport class TranscriptIndex {\n\tprivate indexedDocs: IndexedDoc[] = [];\n\n\tconstructor(docs: TranscriptDoc[]) {\n\t\tthis.indexedDocs = docs.map((doc) => ({\n\t\t\tdoc,\n\t\t\ttokens: tokenize(doc.text),\n\t\t}));\n\t}\n\n\tquery(queryText: string, opts?: { k?: number; minScore?: number; maxSnippetChars?: number }): RecallHit[] {\n\t\tconst k = opts?.k ?? 5;\n\t\tconst minScore = opts?.minScore ?? 0.34;\n\t\tconst maxSnippetChars = opts?.maxSnippetChars ?? 600;\n\n\t\tconst qTokens = tokenize(queryText);\n\t\tif (qTokens.length === 0 || this.indexedDocs.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst qSet = new Set(qTokens);\n\t\tconst hits: RecallHit[] = [];\n\n\t\tfor (const { doc, tokens } of this.indexedDocs) {\n\t\t\tconst dSet = new Set(tokens);\n\t\t\tlet intersection = 0;\n\t\t\tfor (const token of qSet) {\n\t\t\t\tif (dSet.has(token)) {\n\t\t\t\t\tintersection++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst score = intersection / qSet.size;\n\t\t\tif (score <= minScore) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Find matching indices of query tokens in the document text (case-insensitive)\n\t\t\tconst lowerText = doc.text.toLowerCase();\n\t\t\tconst matchIndices: number[] = [];\n\t\t\tfor (const token of qTokens) {\n\t\t\t\tlet pos = lowerText.indexOf(token);\n\t\t\t\twhile (pos !== -1) {\n\t\t\t\t\tmatchIndices.push(pos);\n\t\t\t\t\tpos = lowerText.indexOf(token, pos + 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet bestStart = 0;\n\t\t\tlet bestEnd = Math.min(doc.text.length, maxSnippetChars);\n\n\t\t\tif (matchIndices.length > 0) {\n\t\t\t\tmatchIndices.sort((a, b) => a - b);\n\t\t\t\tlet maxMatchesInWindow = 0;\n\n\t\t\t\tfor (const center of matchIndices) {\n\t\t\t\t\tlet start = Math.max(0, center - Math.floor(maxSnippetChars / 2));\n\t\t\t\t\tconst end = Math.min(doc.text.length, start + maxSnippetChars);\n\t\t\t\t\tif (end - start < maxSnippetChars && start > 0) {\n\t\t\t\t\t\tstart = Math.max(0, end - maxSnippetChars);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet matches = 0;\n\t\t\t\t\tfor (const idx of matchIndices) {\n\t\t\t\t\t\tif (idx >= start && idx < end) {\n\t\t\t\t\t\t\tmatches++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (matches > maxMatchesInWindow) {\n\t\t\t\t\t\tmaxMatchesInWindow = matches;\n\t\t\t\t\t\tbestStart = start;\n\t\t\t\t\t\tbestEnd = end;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rawSnippet = doc.text.slice(bestStart, bestEnd);\n\t\t\tconst prefix = bestStart > 0 ? \"...\" : \"\";\n\t\t\tconst suffix = bestEnd < doc.text.length ? \"...\" : \"\";\n\t\t\tconst snippet = prefix + rawSnippet + suffix;\n\n\t\t\thits.push({\n\t\t\t\tsessionId: doc.sessionId,\n\t\t\t\tscore,\n\t\t\t\tsnippet,\n\t\t\t\ttimestamp: doc.timestamp,\n\t\t\t});\n\t\t}\n\n\t\t// Sort by score desc\n\t\thits.sort((a, b) => b.score - a.score);\n\n\t\treturn hits.slice(0, k);\n\t}\n\n\tget size(): number {\n\t\treturn this.indexedDocs.length;\n\t}\n}\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.58",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "0.80.
|
|
9
|
+
"version": "0.80.58",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-sandbox",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.58",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-sandbox",
|
|
9
|
-
"version": "0.80.
|
|
9
|
+
"version": "0.80.58",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sandbox-runtime": "^0.0.26"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.58",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.80.
|
|
9
|
+
"version": "0.80.58",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caupulican/pi-adaptative",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.61",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@caupulican/pi-adaptative",
|
|
9
|
-
"version": "0.80.
|
|
9
|
+
"version": "0.80.61",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@caupulican/pi-agent-core": "^0.80.
|
|
13
|
-
"@caupulican/pi-ai": "^0.80.
|
|
14
|
-
"@caupulican/pi-tui": "^0.80.
|
|
12
|
+
"@caupulican/pi-agent-core": "^0.80.61",
|
|
13
|
+
"@caupulican/pi-ai": "^0.80.61",
|
|
14
|
+
"@caupulican/pi-tui": "^0.80.61",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"chalk": "5.6.2",
|
|
17
17
|
"cross-spawn": "7.0.6",
|
|
@@ -474,11 +474,11 @@
|
|
|
474
474
|
}
|
|
475
475
|
},
|
|
476
476
|
"node_modules/@caupulican/pi-agent-core": {
|
|
477
|
-
"version": "0.80.
|
|
478
|
-
"resolved": "https://registry.npmjs.org/@caupulican/pi-agent-core/-/pi-agent-core-0.80.
|
|
477
|
+
"version": "0.80.61",
|
|
478
|
+
"resolved": "https://registry.npmjs.org/@caupulican/pi-agent-core/-/pi-agent-core-0.80.61.tgz",
|
|
479
479
|
"license": "MIT",
|
|
480
480
|
"dependencies": {
|
|
481
|
-
"@caupulican/pi-ai": "^0.80.
|
|
481
|
+
"@caupulican/pi-ai": "^0.80.61",
|
|
482
482
|
"ignore": "7.0.5",
|
|
483
483
|
"typebox": "1.1.38",
|
|
484
484
|
"yaml": "2.9.0"
|
|
@@ -488,8 +488,8 @@
|
|
|
488
488
|
}
|
|
489
489
|
},
|
|
490
490
|
"node_modules/@caupulican/pi-ai": {
|
|
491
|
-
"version": "0.80.
|
|
492
|
-
"resolved": "https://registry.npmjs.org/@caupulican/pi-ai/-/pi-ai-0.80.
|
|
491
|
+
"version": "0.80.61",
|
|
492
|
+
"resolved": "https://registry.npmjs.org/@caupulican/pi-ai/-/pi-ai-0.80.61.tgz",
|
|
493
493
|
"license": "MIT",
|
|
494
494
|
"dependencies": {
|
|
495
495
|
"@anthropic-ai/sdk": "0.91.1",
|
|
@@ -511,8 +511,8 @@
|
|
|
511
511
|
}
|
|
512
512
|
},
|
|
513
513
|
"node_modules/@caupulican/pi-tui": {
|
|
514
|
-
"version": "0.80.
|
|
515
|
-
"resolved": "https://registry.npmjs.org/@caupulican/pi-tui/-/pi-tui-0.80.
|
|
514
|
+
"version": "0.80.61",
|
|
515
|
+
"resolved": "https://registry.npmjs.org/@caupulican/pi-tui/-/pi-tui-0.80.61.tgz",
|
|
516
516
|
"license": "MIT",
|
|
517
517
|
"dependencies": {
|
|
518
518
|
"get-east-asian-width": "1.6.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caupulican/pi-adaptative",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.61",
|
|
4
4
|
"description": "Adaptive fork of Pi coding agent for self-evolving agent harness experiments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@caupulican/pi-agent-core": "^0.80.
|
|
45
|
-
"@caupulican/pi-ai": "^0.80.
|
|
46
|
-
"@caupulican/pi-tui": "^0.80.
|
|
44
|
+
"@caupulican/pi-agent-core": "^0.80.61",
|
|
45
|
+
"@caupulican/pi-ai": "^0.80.61",
|
|
46
|
+
"@caupulican/pi-tui": "^0.80.61",
|
|
47
47
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
48
48
|
"chalk": "5.6.2",
|
|
49
49
|
"cross-spawn": "7.0.6",
|