@aexol/spectral 0.9.106 → 0.9.107

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;AAMpC,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;AAsVD,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,7 @@
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 } from "../memory/tools/compact-context.js";
28
29
  import { StringEnum } from "../sdk/ai/index.js";
29
30
  import { mutateSystemPrompt } from "../sdk/coding-agent/core/system-prompt-mutator.js";
30
31
  import { Type } from "typebox";
@@ -35,6 +36,9 @@ import { loadAgentSettings } from "../server/handlers/agent-settings.js";
35
36
  // ---------------------------------------------------------------------------
36
37
  import { createTool, allToolNames } from "../sdk/coding-agent/core/tools/index.js";
37
38
  import { wrapToolDefinition } from "../sdk/coding-agent/core/tools/tool-definition-wrapper.js";
39
+ import { READ_PROJECT_OBSERVATIONS_TOOL_NAME } from "../memory/tools/read-project-observations.js";
40
+ import { RECALL_OBSERVATION_TOOL_NAME } from "../memory/tools/recall-observation.js";
41
+ import { compressSubagentToolResult } from "../memory/tool-output-compressor.js";
38
42
  // ---------------------------------------------------------------------------
39
43
  // Constants
40
44
  // ---------------------------------------------------------------------------
@@ -174,11 +178,29 @@ function resolveModelString(modelStr, modelRegistry) {
174
178
  * `createTool()`. Extension-registered tools (e.g., recall) are resolved
175
179
  * via the ExtensionAPI and wrapped.
176
180
  */
177
- function resolveSubagentTools(toolNames, cwd, ext, ctx) {
178
- if (!toolNames || toolNames.length === 0)
181
+ function cloneSessionEntry(entry) {
182
+ return structuredClone(entry);
183
+ }
184
+ /**
185
+ * Give subagents a point-in-time branch view without exposing the live parent
186
+ * SessionManager. Each getBranch call returns fresh clones of the snapshot.
187
+ */
188
+ export function createSubagentSessionManagerSnapshot(sessionManager) {
189
+ const snapshot = sessionManager.getBranch().map(cloneSessionEntry);
190
+ return {
191
+ getBranch: () => snapshot.map(cloneSessionEntry),
192
+ };
193
+ }
194
+ export function resolveSubagentTools(toolNames, cwd, ext, ctx) {
195
+ const requestedNames = new Set(toolNames ?? []);
196
+ // Project observation search is read-only and can use a minimal, invocation-
197
+ // local context without exposing the parent session manager.
198
+ requestedNames.add(READ_PROJECT_OBSERVATIONS_TOOL_NAME);
199
+ requestedNames.add(RECALL_OBSERVATION_TOOL_NAME);
200
+ if (requestedNames.size === 0)
179
201
  return [];
180
202
  const tools = [];
181
- for (const name of toolNames) {
203
+ for (const name of requestedNames) {
182
204
  // Built-in tools
183
205
  if (allToolNames.has(name)) {
184
206
  try {
@@ -196,7 +218,11 @@ function resolveSubagentTools(toolNames, cwd, ext, ctx) {
196
218
  const richApi = ext;
197
219
  const toolDef = richApi.getToolDefinition(name);
198
220
  if (toolDef) {
199
- tools.push(wrapToolDefinition(toolDef, () => ctx));
221
+ const isolatedContext = name === RECALL_OBSERVATION_TOOL_NAME
222
+ ? { cwd, sessionManager: createSubagentSessionManagerSnapshot(ctx.sessionManager) }
223
+ : { cwd };
224
+ const isolatedContextWithNoParent = isolatedContext;
225
+ tools.push(wrapToolDefinition(toolDef, () => isolatedContextWithNoParent));
200
226
  }
201
227
  }
202
228
  return tools;
@@ -322,12 +348,32 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
322
348
  }
323
349
  };
324
350
  try {
351
+ // Invocation-local hook state; never reuse the parent extension context/runtime.
325
352
  const config = {
326
353
  model,
327
354
  apiKey: auth.apiKey,
328
355
  headers: auth.headers,
329
356
  convertToLlm: (msgs) => msgs.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult"),
330
357
  toolExecution: "parallel",
358
+ transformContext: async (messages) => {
359
+ // Pure request-time DCP adapter: policy-derived, invocation-local, and
360
+ // never connected to the parent Runtime or SessionManager.
361
+ try {
362
+ return buildIsolatedRequestTimePrunedMessages(messages);
363
+ }
364
+ catch {
365
+ return messages;
366
+ }
367
+ },
368
+ afterToolCall: async ({ toolCall, args, result, isError }) => {
369
+ try {
370
+ const content = compressSubagentToolResult(toolCall.name, args, result);
371
+ return content ? { content, details: result.details, isError, terminate: result.terminate } : undefined;
372
+ }
373
+ catch {
374
+ return undefined;
375
+ }
376
+ },
331
377
  };
332
378
  const stream = agentLoop([{ role: "user", content: [{ type: "text", text: task }], timestamp: Date.now() }], {
333
379
  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,9 @@
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";
3
5
  export declare const COMPACT_CONTEXT_TOOL_NAME = "compact_context";
6
+ /** Request-time DCP adapter for isolated loops: it borrows only pure policy helpers. */
7
+ export declare function buildIsolatedRequestTimePrunedMessages(currentMessages: AgentMessage[], policy?: CompactionPolicy, keepRecentTokens?: number): AgentMessage[];
4
8
  export declare function registerCompactContextTool(ext: ExtensionAPI, runtime: Runtime): void;
5
9
  //# 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;AAE7C,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAib3D,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"}
@@ -343,6 +343,20 @@ function createVirtualCompactionEntry(pathEntries, result) {
343
343
  }
344
344
  function buildRequestTimePrunedMessages(ctx, request, policy, currentMessages) {
345
345
  const pathEntries = getBranchEntries(ctx);
346
+ return buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy, currentMessages);
347
+ }
348
+ /** Request-time DCP adapter for isolated loops: it borrows only pure policy helpers. */
349
+ export function buildIsolatedRequestTimePrunedMessages(currentMessages, policy = DEFAULT_COMPACTION_POLICY, keepRecentTokens) {
350
+ const syntheticEntries = currentMessages.map((message, index) => ({
351
+ type: "message",
352
+ id: `isolated-${index}`,
353
+ parentId: index > 0 ? `isolated-${index - 1}` : null,
354
+ timestamp: new Date(index).toISOString(),
355
+ message,
356
+ }));
357
+ return buildRequestTimePrunedMessagesFromEntries(syntheticEntries, { source: "auto", mode: "request_time", scope: "older_history", keepRecentTokens, phaseBoundary: false, minRawTokens: 0 }, policy, currentMessages)?.messages ?? currentMessages;
358
+ }
359
+ function buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy, currentMessages) {
346
360
  const settings = getRequestTimeCompactionSettings(request, policy);
347
361
  const preparation = prepareCompaction(pathEntries, settings, { phaseBoundary: request.phaseBoundary });
348
362
  if (!preparation)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.106",
3
+ "version": "0.9.107",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,