@caupulican/pi-adaptative 0.80.58 → 0.80.59
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 +7 -0
- package/dist/core/agent-session.d.ts +58 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +145 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/learning/reflection-engine.d.ts +57 -0
- package/dist/core/learning/reflection-engine.d.ts.map +1 -0
- package/dist/core/learning/reflection-engine.js +118 -0
- package/dist/core/learning/reflection-engine.js.map +1 -0
- package/dist/core/memory/memory-manager.d.ts +8 -0
- package/dist/core/memory/memory-manager.d.ts.map +1 -1
- package/dist/core/memory/memory-manager.js +14 -2
- package/dist/core/memory/memory-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +13 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +63 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- 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
|
@@ -31,6 +31,7 @@ import { createCoreDiagnosticsToolDefinitions } from "./extensions/builtin.js";
|
|
|
31
31
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
32
32
|
import { disposeExtensionEventSubscriptions } from "./extensions/loader.js";
|
|
33
33
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
34
|
+
import { decideDemand, ReflectionEngine, } from "./learning/reflection-engine.js";
|
|
34
35
|
import { MemoryManager } from "./memory/memory-manager.js";
|
|
35
36
|
import { FileStoreProvider } from "./memory/providers/file-store.js";
|
|
36
37
|
import { compactToolResultDetailsForRetention } from "./message-retention.js";
|
|
@@ -3204,6 +3205,150 @@ export class AgentSession {
|
|
|
3204
3205
|
}
|
|
3205
3206
|
return { cost, reports };
|
|
3206
3207
|
}
|
|
3208
|
+
/**
|
|
3209
|
+
* Run a one-shot LLM completion fully ISOLATED from the main session — the load-bearing
|
|
3210
|
+
* primitive for the native reflection engine (adaptive-agent design §6c/§7).
|
|
3211
|
+
*
|
|
3212
|
+
* Isolation invariants (audited by codex): builds a fresh {@link Context} (no main history), runs
|
|
3213
|
+
* with `tools: []`, sets `cacheRetention: "none"`, and passes **no `sessionId`** — so it cannot
|
|
3214
|
+
* mutate `agent.state.messages`, cannot append session entries, cannot touch the tool registry,
|
|
3215
|
+
* and cannot churn the main session's prompt cache. Mirrors `generateSummary()`'s mechanics.
|
|
3216
|
+
*
|
|
3217
|
+
* Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection
|
|
3218
|
+
* microtask — decide whether to act); it does not throw on a model-level error.
|
|
3219
|
+
*/
|
|
3220
|
+
async runIsolatedCompletion(opts) {
|
|
3221
|
+
const model = opts.model ?? this.model;
|
|
3222
|
+
if (!model) {
|
|
3223
|
+
throw new Error("runIsolatedCompletion: no model available");
|
|
3224
|
+
}
|
|
3225
|
+
const thinkingLevel = opts.thinkingLevel ?? "off";
|
|
3226
|
+
// Fresh, isolated context: explicit messages, no tools, nothing from the main session.
|
|
3227
|
+
const context = {
|
|
3228
|
+
systemPrompt: opts.systemPrompt,
|
|
3229
|
+
messages: opts.messages,
|
|
3230
|
+
tools: [],
|
|
3231
|
+
};
|
|
3232
|
+
// Isolate the prompt cache and DELIBERATELY omit sessionId so no session-aware caching/routing
|
|
3233
|
+
// can entangle this call with the main session.
|
|
3234
|
+
const options = {
|
|
3235
|
+
maxTokens: opts.maxTokens,
|
|
3236
|
+
signal: opts.signal,
|
|
3237
|
+
cacheRetention: "none",
|
|
3238
|
+
};
|
|
3239
|
+
// pi-ai's `reasoning` option does not include "off" (that's the provider default already).
|
|
3240
|
+
if (thinkingLevel !== "off") {
|
|
3241
|
+
options.reasoning = thinkingLevel;
|
|
3242
|
+
}
|
|
3243
|
+
// When streamFn is the raw streamSimple (e.g. in tests), auth must be injected explicitly.
|
|
3244
|
+
// Throw only when auth genuinely fails — providers that authenticate without an API key
|
|
3245
|
+
// (OAuth, local no-key) legitimately return ok with an undefined apiKey.
|
|
3246
|
+
if (this.agent.streamFn === streamSimple) {
|
|
3247
|
+
const auth = await this._modelRegistry.getApiKeyAndHeaders(model);
|
|
3248
|
+
if (!auth.ok) {
|
|
3249
|
+
throw new Error(auth.error);
|
|
3250
|
+
}
|
|
3251
|
+
options.apiKey = auth.apiKey;
|
|
3252
|
+
options.headers = auth.headers;
|
|
3253
|
+
}
|
|
3254
|
+
const stream = await this.agent.streamFn(model, context, options);
|
|
3255
|
+
const result = await stream.result();
|
|
3256
|
+
const text = result.content
|
|
3257
|
+
.filter((c) => c.type === "text")
|
|
3258
|
+
.map((c) => c.text)
|
|
3259
|
+
.join("");
|
|
3260
|
+
const usage = result.usage ?? {
|
|
3261
|
+
input: 0,
|
|
3262
|
+
output: 0,
|
|
3263
|
+
cacheRead: 0,
|
|
3264
|
+
cacheWrite: 0,
|
|
3265
|
+
totalTokens: 0,
|
|
3266
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
3267
|
+
};
|
|
3268
|
+
return { text, usage, stopReason: result.stopReason };
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the
|
|
3272
|
+
* {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the
|
|
3273
|
+
* resulting memory writes through the bundled `memory` tool, and accounts the reflection's token
|
|
3274
|
+
* cost via the cost-aggregation surface so it stays visible and net-negative-auditable.
|
|
3275
|
+
*
|
|
3276
|
+
* Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass
|
|
3277
|
+
* is best-effort: a model/parse error yields no writes, never throws into the caller.
|
|
3278
|
+
*/
|
|
3279
|
+
async runReflectionPass(input) {
|
|
3280
|
+
if (this._isChildSession)
|
|
3281
|
+
return null;
|
|
3282
|
+
const plan = decideDemand(input.signals);
|
|
3283
|
+
if (plan.act === "skip")
|
|
3284
|
+
return null;
|
|
3285
|
+
const complete = (systemPrompt, userPrompt) => this.runIsolatedCompletion({
|
|
3286
|
+
systemPrompt,
|
|
3287
|
+
messages: [{ role: "user", content: [{ type: "text", text: userPrompt }], timestamp: Date.now() }],
|
|
3288
|
+
model: input.model,
|
|
3289
|
+
thinkingLevel: input.thinkingLevel ?? "low",
|
|
3290
|
+
maxTokens: plan.tokenBudget,
|
|
3291
|
+
signal: input.signal,
|
|
3292
|
+
});
|
|
3293
|
+
const result = await new ReflectionEngine().reflect({
|
|
3294
|
+
recentTurnText: input.recentTurnText,
|
|
3295
|
+
// Read memory FRESH (not the prefix-cache-frozen system-prompt block) so confront-before-write
|
|
3296
|
+
// sees writes made earlier this session.
|
|
3297
|
+
existingMemory: this._memoryManager.buildSystemPromptBlockFresh() || "",
|
|
3298
|
+
plan,
|
|
3299
|
+
complete,
|
|
3300
|
+
});
|
|
3301
|
+
for (const write of result.writes) {
|
|
3302
|
+
await this._applyReflectionWrite(write, input.signal);
|
|
3303
|
+
}
|
|
3304
|
+
// Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).
|
|
3305
|
+
// Idempotent on reportId so a retried/duplicated pass cannot double-count.
|
|
3306
|
+
if (result.usage.cost.total > 0 || result.usage.totalTokens > 0) {
|
|
3307
|
+
this.addSpawnedUsage(result.usage, { label: "reflection", reportId: input.reportId });
|
|
3308
|
+
}
|
|
3309
|
+
return result;
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove`
|
|
3313
|
+
* don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring
|
|
3314
|
+
* isn't found there. Best-effort: failures are swallowed (reflection must never break a turn).
|
|
3315
|
+
*/
|
|
3316
|
+
async _applyReflectionWrite(write, signal) {
|
|
3317
|
+
const memTool = this._memoryManager.getToolDefinitions().find((t) => t.name === "memory");
|
|
3318
|
+
const exec = memTool?.execute;
|
|
3319
|
+
if (!exec)
|
|
3320
|
+
return;
|
|
3321
|
+
const run = (params) => exec("reflection", params, signal, undefined, undefined);
|
|
3322
|
+
if (write.kind === "memory_add") {
|
|
3323
|
+
try {
|
|
3324
|
+
await run({ action: "add", target: write.section === "USER" ? "user" : "memory", content: write.text });
|
|
3325
|
+
}
|
|
3326
|
+
catch {
|
|
3327
|
+
// best-effort; reflection writes must never throw into the turn loop
|
|
3328
|
+
}
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
// replace / remove carry no target file — try MEMORY.md, then USER.md. The memory tool reports
|
|
3332
|
+
// outcomes via `details.success` (it catches its own errors rather than throwing). Only a
|
|
3333
|
+
// genuine "not found in the file" justifies trying the other file; a real failure for a file
|
|
3334
|
+
// (budget exceeded / drift) must NOT fall through and mutate the wrong target.
|
|
3335
|
+
for (const target of ["memory", "user"]) {
|
|
3336
|
+
try {
|
|
3337
|
+
const params = write.kind === "memory_replace"
|
|
3338
|
+
? { action: "replace", target, oldContent: write.target, content: write.text }
|
|
3339
|
+
: { action: "remove", target, oldContent: write.target };
|
|
3340
|
+
const res = await run(params);
|
|
3341
|
+
if (res?.details?.success === true)
|
|
3342
|
+
return; // applied
|
|
3343
|
+
if (!/not found/i.test(String(res?.details?.error ?? "")))
|
|
3344
|
+
return; // real failure — don't misapply
|
|
3345
|
+
// substring simply absent from this file — try the next target
|
|
3346
|
+
}
|
|
3347
|
+
catch {
|
|
3348
|
+
// defensive: if the tool ever does throw, try the next target
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3207
3352
|
getContextUsage() {
|
|
3208
3353
|
const model = this.model;
|
|
3209
3354
|
if (!model)
|