@caupulican/pi-adaptative 0.80.57 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli/args.d.ts +2 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +5 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +116 -1
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +260 -0
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/extensions/loader.d.ts.map +1 -1
  11. package/dist/core/extensions/loader.js +5 -0
  12. package/dist/core/extensions/loader.js.map +1 -1
  13. package/dist/core/extensions/runner.d.ts.map +1 -1
  14. package/dist/core/extensions/runner.js +1 -0
  15. package/dist/core/extensions/runner.js.map +1 -1
  16. package/dist/core/extensions/types.d.ts +19 -1
  17. package/dist/core/extensions/types.d.ts.map +1 -1
  18. package/dist/core/extensions/types.js.map +1 -1
  19. package/dist/core/learning/reflection-engine.d.ts +57 -0
  20. package/dist/core/learning/reflection-engine.d.ts.map +1 -0
  21. package/dist/core/learning/reflection-engine.js +118 -0
  22. package/dist/core/learning/reflection-engine.js.map +1 -0
  23. package/dist/core/memory/memory-manager.d.ts +8 -0
  24. package/dist/core/memory/memory-manager.d.ts.map +1 -1
  25. package/dist/core/memory/memory-manager.js +14 -2
  26. package/dist/core/memory/memory-manager.js.map +1 -1
  27. package/dist/main.d.ts.map +1 -1
  28. package/dist/main.js +1 -0
  29. package/dist/main.js.map +1 -1
  30. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  31. package/dist/modes/interactive/components/footer.js +11 -3
  32. package/dist/modes/interactive/components/footer.js.map +1 -1
  33. package/dist/modes/interactive/interactive-mode.d.ts +13 -0
  34. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  35. package/dist/modes/interactive/interactive-mode.js +63 -1
  36. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  37. package/dist/modes/print-mode.d.ts +13 -0
  38. package/dist/modes/print-mode.d.ts.map +1 -1
  39. package/dist/modes/print-mode.js +18 -1
  40. package/dist/modes/print-mode.js.map +1 -1
  41. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  42. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  43. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  44. package/examples/extensions/sandbox/package-lock.json +2 -2
  45. package/examples/extensions/sandbox/package.json +1 -1
  46. package/examples/extensions/with-deps/package-lock.json +2 -2
  47. package/examples/extensions/with-deps/package.json +1 -1
  48. package/npm-shrinkwrap.json +12 -12
  49. 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";
@@ -59,6 +60,8 @@ export function parseSkillBlock(text) {
59
60
  userMessage: match[4]?.trim() || undefined,
60
61
  };
61
62
  }
63
+ /** customType for spawned-usage roll-up entries (Cost Aggregation, Model A). */
64
+ export const SPAWNED_USAGE_CUSTOM_TYPE = "spawned_usage";
62
65
  // ============================================================================
63
66
  // Constants
64
67
  // ============================================================================
@@ -2082,6 +2085,9 @@ export class AgentSession {
2082
2085
  setThinkingLevel: (level) => this.setThinkingLevel(level),
2083
2086
  getExternalResourceRoots: () => this.settingsManager.getEffectiveExternalResourceRoots(),
2084
2087
  registerMemoryProvider: (provider) => this.registerMemoryProvider(provider),
2088
+ reportSpawnedUsage: (usage, opts) => {
2089
+ this.addSpawnedUsage(usage, opts);
2090
+ },
2085
2091
  }, {
2086
2092
  getModel: () => this.model,
2087
2093
  isIdle: () => !this.isStreaming,
@@ -3089,6 +3095,260 @@ export class AgentSession {
3089
3095
  contextUsage: this.getContextUsage(),
3090
3096
  };
3091
3097
  }
3098
+ /**
3099
+ * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own
3100
+ * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of
3101
+ * truth for "how much did this session and everything it spawned spend" — used by print-mode
3102
+ * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.
3103
+ *
3104
+ * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child
3105
+ * that itself spawned grandchildren must report own + sub-usage in one number, or the parent
3106
+ * silently under-counts the grandchildren.
3107
+ */
3108
+ getCumulativeUsage() {
3109
+ let input = 0;
3110
+ let output = 0;
3111
+ let cacheRead = 0;
3112
+ let cacheWrite = 0;
3113
+ let totalTokens = 0;
3114
+ let costInput = 0;
3115
+ let costOutput = 0;
3116
+ let costCacheRead = 0;
3117
+ let costCacheWrite = 0;
3118
+ let costTotal = 0;
3119
+ const add = (usage) => {
3120
+ input += usage.input;
3121
+ output += usage.output;
3122
+ cacheRead += usage.cacheRead;
3123
+ cacheWrite += usage.cacheWrite;
3124
+ totalTokens += usage.totalTokens;
3125
+ costInput += usage.cost.input;
3126
+ costOutput += usage.cost.output;
3127
+ costCacheRead += usage.cost.cacheRead;
3128
+ costCacheWrite += usage.cost.cacheWrite;
3129
+ costTotal += usage.cost.total;
3130
+ };
3131
+ for (const message of this.state.messages) {
3132
+ if (message.role !== "assistant")
3133
+ continue;
3134
+ const usage = message.usage;
3135
+ if (!usage)
3136
+ continue;
3137
+ add(usage);
3138
+ }
3139
+ // Roll up usage this session attributed to its own spawned children (single-hop).
3140
+ for (const entry of this.sessionManager.getEntries()) {
3141
+ if (entry.type !== "custom" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE)
3142
+ continue;
3143
+ const data = entry.data;
3144
+ if (data?.usage)
3145
+ add(data.usage);
3146
+ }
3147
+ return {
3148
+ input,
3149
+ output,
3150
+ cacheRead,
3151
+ cacheWrite,
3152
+ totalTokens,
3153
+ cost: {
3154
+ input: costInput,
3155
+ output: costOutput,
3156
+ cacheRead: costCacheRead,
3157
+ cacheWrite: costCacheWrite,
3158
+ total: costTotal,
3159
+ },
3160
+ };
3161
+ }
3162
+ /**
3163
+ * Record usage spent by a spawned/subagent session so the footer can roll it into the
3164
+ * displayed cost. Persisted as a `CustomEntry` (`customType: "spawned_usage"`, Model A) so
3165
+ * it survives reload and is reconstructed exactly like main usage; a new/forked session
3166
+ * starts fresh because it owns a new log file.
3167
+ *
3168
+ * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a
3169
+ * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop
3170
+ * invariant documented on {@link SpawnedUsageReport}.
3171
+ *
3172
+ * @returns the id of the appended entry, or `undefined` if the report was a duplicate.
3173
+ */
3174
+ addSpawnedUsage(usage, opts) {
3175
+ const reportId = opts?.reportId;
3176
+ if (reportId) {
3177
+ for (const entry of this.sessionManager.getEntries()) {
3178
+ if (entry.type === "custom" &&
3179
+ entry.customType === SPAWNED_USAGE_CUSTOM_TYPE &&
3180
+ entry.data?.reportId === reportId) {
3181
+ return undefined;
3182
+ }
3183
+ }
3184
+ }
3185
+ const report = {
3186
+ usage,
3187
+ label: opts?.label,
3188
+ sourceSessionId: opts?.sourceSessionId,
3189
+ reportId,
3190
+ };
3191
+ return this.sessionManager.appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);
3192
+ }
3193
+ /** Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). */
3194
+ getSpawnedUsage() {
3195
+ let cost = 0;
3196
+ let reports = 0;
3197
+ for (const entry of this.sessionManager.getEntries()) {
3198
+ if (entry.type !== "custom" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE)
3199
+ continue;
3200
+ const data = entry.data;
3201
+ if (!data?.usage)
3202
+ continue;
3203
+ cost += data.usage.cost.total;
3204
+ reports += 1;
3205
+ }
3206
+ return { cost, reports };
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
+ }
3092
3352
  getContextUsage() {
3093
3353
  const model = this.model;
3094
3354
  if (!model)