@aexol/spectral 0.9.106 → 0.9.108

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.
@@ -24,11 +24,19 @@
24
24
  * - Signal propagation (abort passthrough)
25
25
  * - Access to observational memory via the recall tool
26
26
  */
27
- import { type ExtensionAPI } from "../sdk/coding-agent/index.js";
27
+ import type { AgentTool } from "../sdk/agent-core/index.js";
28
+ import { type ExtensionAPI, type ExtensionContext } from "../sdk/coding-agent/index.js";
28
29
  import { type AgentConfig } from "./agents.js";
29
30
  import type { ServerEvent } from "../server/wire.ts";
31
+ import type { ExtensionContext as RichExtensionContext } from "../sdk/coding-agent/core/extensions/types.js";
30
32
  export declare function stripInjectedPrimaryAgentPrompt(systemPrompt: string | undefined): string | undefined;
31
33
  export declare function buildSubagentSystemPrompt(baseSystemPrompt: string | undefined, agent: Pick<AgentConfig, "name" | "systemPrompt">): string;
34
+ /**
35
+ * Give subagents a point-in-time branch view without exposing the live parent
36
+ * SessionManager. Each getBranch call returns fresh clones of the snapshot.
37
+ */
38
+ export declare function createSubagentSessionManagerSnapshot(sessionManager: ExtensionContext["sessionManager"]): Pick<RichExtensionContext["sessionManager"], "getBranch">;
39
+ export declare function resolveSubagentTools(toolNames: string[] | undefined, cwd: string, ext: ExtensionAPI, ctx: ExtensionContext): AgentTool[];
32
40
  export default function subagentExtension(ext: ExtensionAPI, opts?: {
33
41
  disabledAgents?: string[];
34
42
  emitSubagentEvent?: (event: ServerEvent) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAYH,OAAO,EACN,KAAK,YAAY,EAEjB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,KAAK,WAAW,EAAmC,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAsFrD,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpG;AAED,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAAC,GAC/C,MAAM,CAKR;AAkdD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CAAE,GACpF,IAAI,CAgZN"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAIX,SAAS,EAET,MAAM,4BAA4B,CAAC;AAUpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,KAAK,WAAW,EAAmC,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOrD,OAAO,KAAK,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AAmF7G,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpG;AAED,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAAC,GAC/C,MAAM,CAKR;AAiHD;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,GAChD,IAAI,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,CAK3D;AAED,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,GACnB,SAAS,EAAE,CAqCb;AA0VD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CAAE,GACpF,IAAI,CAgZN"}
@@ -25,6 +25,8 @@
25
25
  * - Access to observational memory via the recall tool
26
26
  */
27
27
  import { agentLoop } from "../sdk/agent-core/index.js";
28
+ import { buildIsolatedRequestTimePrunedMessages, createIsolatedDcpNudgePayloadHook, } from "../memory/tools/compact-context.js";
29
+ import { loadConfig } from "../memory/config.js";
28
30
  import { StringEnum } from "../sdk/ai/index.js";
29
31
  import { mutateSystemPrompt } from "../sdk/coding-agent/core/system-prompt-mutator.js";
30
32
  import { Type } from "typebox";
@@ -35,6 +37,9 @@ import { loadAgentSettings } from "../server/handlers/agent-settings.js";
35
37
  // ---------------------------------------------------------------------------
36
38
  import { createTool, allToolNames } from "../sdk/coding-agent/core/tools/index.js";
37
39
  import { wrapToolDefinition } from "../sdk/coding-agent/core/tools/tool-definition-wrapper.js";
40
+ import { READ_PROJECT_OBSERVATIONS_TOOL_NAME } from "../memory/tools/read-project-observations.js";
41
+ import { RECALL_OBSERVATION_TOOL_NAME } from "../memory/tools/recall-observation.js";
42
+ import { compressSubagentToolResult } from "../memory/tool-output-compressor.js";
38
43
  // ---------------------------------------------------------------------------
39
44
  // Constants
40
45
  // ---------------------------------------------------------------------------
@@ -174,11 +179,29 @@ function resolveModelString(modelStr, modelRegistry) {
174
179
  * `createTool()`. Extension-registered tools (e.g., recall) are resolved
175
180
  * via the ExtensionAPI and wrapped.
176
181
  */
177
- function resolveSubagentTools(toolNames, cwd, ext, ctx) {
178
- if (!toolNames || toolNames.length === 0)
182
+ function cloneSessionEntry(entry) {
183
+ return structuredClone(entry);
184
+ }
185
+ /**
186
+ * Give subagents a point-in-time branch view without exposing the live parent
187
+ * SessionManager. Each getBranch call returns fresh clones of the snapshot.
188
+ */
189
+ export function createSubagentSessionManagerSnapshot(sessionManager) {
190
+ const snapshot = sessionManager.getBranch().map(cloneSessionEntry);
191
+ return {
192
+ getBranch: () => snapshot.map(cloneSessionEntry),
193
+ };
194
+ }
195
+ export function resolveSubagentTools(toolNames, cwd, ext, ctx) {
196
+ const requestedNames = new Set(toolNames ?? []);
197
+ // Project observation search is read-only and can use a minimal, invocation-
198
+ // local context without exposing the parent session manager.
199
+ requestedNames.add(READ_PROJECT_OBSERVATIONS_TOOL_NAME);
200
+ requestedNames.add(RECALL_OBSERVATION_TOOL_NAME);
201
+ if (requestedNames.size === 0)
179
202
  return [];
180
203
  const tools = [];
181
- for (const name of toolNames) {
204
+ for (const name of requestedNames) {
182
205
  // Built-in tools
183
206
  if (allToolNames.has(name)) {
184
207
  try {
@@ -196,7 +219,11 @@ function resolveSubagentTools(toolNames, cwd, ext, ctx) {
196
219
  const richApi = ext;
197
220
  const toolDef = richApi.getToolDefinition(name);
198
221
  if (toolDef) {
199
- tools.push(wrapToolDefinition(toolDef, () => ctx));
222
+ const isolatedContext = name === RECALL_OBSERVATION_TOOL_NAME
223
+ ? { cwd, sessionManager: createSubagentSessionManagerSnapshot(ctx.sessionManager) }
224
+ : { cwd };
225
+ const isolatedContextWithNoParent = isolatedContext;
226
+ tools.push(wrapToolDefinition(toolDef, () => isolatedContextWithNoParent));
200
227
  }
201
228
  }
202
229
  return tools;
@@ -322,12 +349,36 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
322
349
  }
323
350
  };
324
351
  try {
352
+ // Invocation-local hook state; never reuse the parent extension context/runtime.
325
353
  const config = {
326
354
  model,
327
355
  apiKey: auth.apiKey,
356
+ onPayload: createIsolatedDcpNudgePayloadHook({
357
+ config: loadConfig(effectiveCwd),
358
+ policy: ctx.settingsManager?.getCompactionPolicy?.(),
359
+ }),
328
360
  headers: auth.headers,
329
361
  convertToLlm: (msgs) => msgs.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult"),
330
362
  toolExecution: "parallel",
363
+ transformContext: async (messages) => {
364
+ // Pure request-time DCP adapter: policy-derived, invocation-local, and
365
+ // never connected to the parent Runtime or SessionManager.
366
+ try {
367
+ return buildIsolatedRequestTimePrunedMessages(messages);
368
+ }
369
+ catch {
370
+ return messages;
371
+ }
372
+ },
373
+ afterToolCall: async ({ toolCall, args, result, isError }) => {
374
+ try {
375
+ const content = compressSubagentToolResult(toolCall.name, args, result);
376
+ return content ? { content, details: result.details, isError, terminate: result.terminate } : undefined;
377
+ }
378
+ catch {
379
+ return undefined;
380
+ }
381
+ },
331
382
  };
332
383
  const stream = agentLoop([{ role: "user", content: [{ type: "text", text: task }], timestamp: Date.now() }], {
333
384
  systemPrompt: effectivePrompt,
@@ -51,5 +51,21 @@ export interface ToolOutputCompressionTelemetry {
51
51
  }
52
52
  export declare function getToolOutputCompressionTelemetry(runtime: Runtime): ToolOutputCompressionTelemetry;
53
53
  export declare function resetToolOutputCompressionTelemetry(runtime: Runtime): void;
54
+ /** Compress a subagent tool result without using the parent Runtime or extension lifecycle. */
55
+ export declare function compressSubagentToolResult(toolName: string, input: unknown, result: {
56
+ content: Array<{
57
+ type: string;
58
+ text?: string;
59
+ data?: string;
60
+ mimeType?: string;
61
+ }>;
62
+ }): Array<{
63
+ type: "text";
64
+ text: string;
65
+ } | {
66
+ type: "image";
67
+ data: string;
68
+ mimeType: string;
69
+ }> | undefined;
54
70
  export declare function registerToolOutputCompressor(ext: ExtensionAPI, runtime: Runtime): void;
55
71
  //# sourceMappingURL=tool-output-compressor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-output-compressor.d.ts","sourceRoot":"","sources":["../../src/memory/tool-output-compressor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAWjE,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAO5C,MAAM,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAE1D,eAAO,MAAM,yBAAyB,EAAE,gBAEvC,CAAC;AAqyBF,MAAM,WAAW,gBAAgB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3F;AA8BD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,OAAO,GAAG,8BAA8B,CAElG;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE1E;AAkDD,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,OAAO,GACd,IAAI,CAqEN"}
1
+ {"version":3,"file":"tool-output-compressor.d.ts","sourceRoot":"","sources":["../../src/memory/tool-output-compressor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAWjE,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAO5C,MAAM,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAE1D,eAAO,MAAM,yBAAyB,EAAE,gBAEvC,CAAC;AAqyBF,MAAM,WAAW,gBAAgB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3F;AA8BD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,OAAO,GAAG,8BAA8B,CAElG;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE1E;AAkDD,+FAA+F;AAC/F,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAC3F,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,CAevG;AAED,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,OAAO,GACd,IAAI,CAqEN"}
@@ -766,6 +766,30 @@ function compressedContentResult(runtime, event, toolName, original, compressed)
766
766
  content: [...event.content.filter((c) => c.type !== "text"), { type: "text", text: replacementText }],
767
767
  };
768
768
  }
769
+ /** Compress a subagent tool result without using the parent Runtime or extension lifecycle. */
770
+ export function compressSubagentToolResult(toolName, input, result) {
771
+ const safeInput = input && typeof input === "object" && !Array.isArray(input) ? input : {};
772
+ const text = result.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
773
+ if (!text || text.startsWith("[tool-output-compressor "))
774
+ return undefined;
775
+ const event = { content: result.content, input: safeInput, toolName };
776
+ let compressed;
777
+ const config = DEFAULT_COMPRESSOR_CONFIG;
778
+ if (toolName === "read")
779
+ compressed = compressReadOutput(event, config);
780
+ else if (toolName === "bash")
781
+ compressed = compressBashOutput(event, config);
782
+ else if (toolName === "ls")
783
+ compressed = compressLsOutput(event, config);
784
+ else if (toolName === "grep")
785
+ compressed = compressGrepOutput(event, config);
786
+ else if (toolName === "find")
787
+ compressed = compressFindOutput(event, config);
788
+ if (!compressed || compressed === text)
789
+ return undefined;
790
+ const compressedContent = result.content.map((part) => part.type === "text" ? { type: "text", text: part.text ?? "" } : part.type === "image" && part.data && part.mimeType ? { type: "image", data: part.data, mimeType: part.mimeType } : undefined).filter((part) => part !== undefined && part.type !== "text");
791
+ return [{ type: "text", text: `${formatCompressionHeader({ toolName, originalChars: text.length, compressedChars: compressed.length, savingsPercent: Math.round((1 - compressed.length / Math.max(1, text.length)) * 10_000) / 100 })}\n${compressed}` }, ...compressedContent];
792
+ }
769
793
  export function registerToolOutputCompressor(ext, runtime) {
770
794
  ext.on("tool_result", (_event, _ctx) => {
771
795
  const config = resolveCompressorConfig(_ctx);
@@ -1,5 +1,19 @@
1
+ import type { AgentMessage } from "../../sdk/agent-core/index.js";
2
+ import { type CompactionPolicy } from "../../sdk/coding-agent/core/compaction/index.js";
1
3
  import { type ExtensionAPI } from "../../sdk/coding-agent/index.js";
2
4
  import type { Runtime } from "../runtime.js";
5
+ import { type Config } from "../config.js";
3
6
  export declare const COMPACT_CONTEXT_TOOL_NAME = "compact_context";
7
+ /**
8
+ * Creates an invocation-local provider payload hook for isolated agent loops.
9
+ * The values are snapshotted at invocation start; no Runtime, SessionManager, or
10
+ * extension state is retained by the returned hook.
11
+ */
12
+ export declare function createIsolatedDcpNudgePayloadHook(options?: {
13
+ config?: Pick<Config, "dcpInRunNudgeIterationThreshold" | "dcpInRunNudgeFrequency">;
14
+ policy?: CompactionPolicy;
15
+ }): NonNullable<import("../../sdk/ai/index.js").SimpleStreamOptions["onPayload"]>;
16
+ /** Request-time DCP adapter for isolated loops: it borrows only pure policy helpers. */
17
+ export declare function buildIsolatedRequestTimePrunedMessages(currentMessages: AgentMessage[], policy?: CompactionPolicy, keepRecentTokens?: number): AgentMessage[];
4
18
  export declare function registerCompactContextTool(ext: ExtensionAPI, runtime: Runtime): void;
5
19
  //# sourceMappingURL=compact-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compact-context.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/compact-context.ts"],"names":[],"mappings":"AAaA,OAAO,EAAc,KAAK,YAAY,EAAyB,MAAM,iCAAiC,CAAC;AAIvG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AA4qB3D,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAkSpF"}
1
+ {"version":3,"file":"compact-context.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/compact-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EASN,KAAK,gBAAgB,EACrB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAc,KAAK,YAAY,EAAyB,MAAM,iCAAiC,CAAC;AAKvG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAErD,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAoO3D;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,GAAE;IAC1D,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,iCAAiC,GAAG,wBAAwB,CAAC,CAAC;IACpF,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACrB,GAAG,WAAW,CAAC,OAAO,uBAAuB,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAiBrF;AA+MD,wFAAwF;AACxF,wBAAgB,sCAAsC,CACrD,eAAe,EAAE,YAAY,EAAE,EAC/B,MAAM,GAAE,gBAA4C,EACpD,gBAAgB,CAAC,EAAE,MAAM,GACvB,YAAY,EAAE,CAchB;AAsQD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAkSpF"}
@@ -4,6 +4,7 @@ import { defineTool } from "../../sdk/coding-agent/index.js";
4
4
  import { buildSessionContext } from "../../sdk/coding-agent/core/session-manager.js";
5
5
  import { rawTokensSinceLastCompaction } from "../branch.js";
6
6
  import { debugLog, withDebugLogContext } from "../debug-log.js";
7
+ import { DEFAULTS } from "../config.js";
7
8
  export const COMPACT_CONTEXT_TOOL_NAME = "compact_context";
8
9
  const DCP_NUDGE_HEADING = "## DCP-lite context hygiene";
9
10
  const MAX_COMPACT_CONTEXT_TEXT_CHARS = 240;
@@ -194,6 +195,31 @@ function injectDcpNudgeIntoProviderPayload(payload, nudge) {
194
195
  return undefined;
195
196
  return injectDcpNudgeIntoAnthropicPayload(payload, nudge) ?? injectDcpNudgeIntoOpenAiPayload(payload, nudge);
196
197
  }
198
+ /**
199
+ * Creates an invocation-local provider payload hook for isolated agent loops.
200
+ * The values are snapshotted at invocation start; no Runtime, SessionManager, or
201
+ * extension state is retained by the returned hook.
202
+ */
203
+ export function createIsolatedDcpNudgePayloadHook(options = {}) {
204
+ let providerRequestCount = 0;
205
+ let lastNudgeRequest = 0;
206
+ const config = options.config ?? DEFAULTS;
207
+ const policy = options.policy ?? DEFAULT_COMPACTION_POLICY;
208
+ return (payload) => {
209
+ providerRequestCount += 1;
210
+ const frequency = config.dcpInRunNudgeFrequency;
211
+ const hasSignal = providerRequestCount >= config.dcpInRunNudgeIterationThreshold;
212
+ const frequencyReady = lastNudgeRequest === 0 || providerRequestCount - lastNudgeRequest >= frequency;
213
+ if (!hasSignal || !frequencyReady || payloadContainsDcpNudge(payload))
214
+ return undefined;
215
+ const nudge = buildDcpNudge(0, undefined, undefined, policy, providerRequestCount);
216
+ const nextPayload = injectDcpNudgeIntoProviderPayload(payload, nudge);
217
+ if (nextPayload === undefined)
218
+ return undefined;
219
+ lastNudgeRequest = providerRequestCount;
220
+ return nextPayload;
221
+ };
222
+ }
197
223
  function rawTokensForContext(ctx) {
198
224
  const entries = ctx.sessionManager.getBranch();
199
225
  return rawTokensSinceLastCompaction(entries);
@@ -343,6 +369,20 @@ function createVirtualCompactionEntry(pathEntries, result) {
343
369
  }
344
370
  function buildRequestTimePrunedMessages(ctx, request, policy, currentMessages) {
345
371
  const pathEntries = getBranchEntries(ctx);
372
+ return buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy, currentMessages);
373
+ }
374
+ /** Request-time DCP adapter for isolated loops: it borrows only pure policy helpers. */
375
+ export function buildIsolatedRequestTimePrunedMessages(currentMessages, policy = DEFAULT_COMPACTION_POLICY, keepRecentTokens) {
376
+ const syntheticEntries = currentMessages.map((message, index) => ({
377
+ type: "message",
378
+ id: `isolated-${index}`,
379
+ parentId: index > 0 ? `isolated-${index - 1}` : null,
380
+ timestamp: new Date(index).toISOString(),
381
+ message,
382
+ }));
383
+ return buildRequestTimePrunedMessagesFromEntries(syntheticEntries, { source: "auto", mode: "request_time", scope: "older_history", keepRecentTokens, phaseBoundary: false, minRawTokens: 0 }, policy, currentMessages)?.messages ?? currentMessages;
384
+ }
385
+ function buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy, currentMessages) {
346
386
  const settings = getRequestTimeCompactionSettings(request, policy);
347
387
  const preparation = prepareCompaction(pathEntries, settings, { phaseBoundary: request.phaseBoundary });
348
388
  if (!preparation)
@@ -27,6 +27,7 @@ export declare function fauxAssistantMessage(content: string | FauxContentBlock
27
27
  }): AssistantMessage;
28
28
  export type FauxResponseFactory = (context: Context, options: StreamOptions | undefined, state: {
29
29
  callCount: number;
30
+ lastPayload?: unknown;
30
31
  }, model: Model<string>) => AssistantMessage | Promise<AssistantMessage>;
31
32
  export type FauxResponseStep = AssistantMessage | FauxResponseFactory;
32
33
  export interface RegisterFauxProviderOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"faux.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/faux.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"faux.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/faux.ts"],"names":[],"mappings":"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"}
@@ -317,6 +317,15 @@ export function registerFauxProvider(options = {}) {
317
317
  state.callCount++;
318
318
  queueMicrotask(async () => {
319
319
  try {
320
+ const payload = {
321
+ system: context.systemPrompt,
322
+ messages: context.messages,
323
+ tools: context.tools,
324
+ };
325
+ // Faux is intentionally a low-level provider: exercise the same payload
326
+ // interception seam as HTTP providers so agent-loop continuations can be
327
+ // tested without network calls.
328
+ state.lastPayload = (await streamOptions?.onPayload?.(payload, requestModel)) ?? payload;
320
329
  await streamOptions?.onResponse?.({ status: 200, headers: {} }, requestModel);
321
330
  if (!step) {
322
331
  let message = createErrorMessage(new Error("No more faux responses queued"), api, provider, requestModel.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.106",
3
+ "version": "0.9.108",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,