f82a5d121f
- Added task.maxRecursionDepth setting to control how many levels deep subagents can spawn their own subagents (0=none, 1=one level, 2=two levels, -1=unlimited). - Added nested task artifact naming with parent task prefixes (e.g., '0-Auth.1-Subtask') to support hierarchical task identification. - Added taskDepth and parentTaskPrefix options to CreateAgentSessionOptions for supporting nested/subagent sessions. - Changed task tool spawns configuration from 'explore' to '*' to allow spawning any type of subagent. - Updated system prompt to unconditionally include parallel delegation guidance for all agent types instead of only coordinators. - Implemented automatic task tool disabling at maximum recursion depth to prevent excessive task nesting.
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
/**
|
|
* Session-scoped manager for agent output IDs.
|
|
*
|
|
* Ensures unique output IDs across task tool invocations within a session.
|
|
* Prefixes each ID with a sequential number (e.g., "0-AuthProvider", "1-AuthApi").
|
|
* If a parent prefix is provided, IDs are nested (e.g., "0-Auth.1-Subtask").
|
|
*
|
|
* This enables reliable agent:// URL resolution and prevents artifact collisions.
|
|
*/
|
|
import * as fs from "node:fs/promises";
|
|
|
|
function escapeRegExp(value: string): string {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
/**
|
|
* Manages agent output ID allocation to ensure uniqueness.
|
|
*
|
|
* Each allocated ID gets a numeric prefix based on allocation order.
|
|
* If configured with a parent prefix, the numeric prefix is appended after
|
|
* the parent (e.g., "0-Parent.0-Child").
|
|
* On resume, scans existing files to find the next available index.
|
|
*/
|
|
export class AgentOutputManager {
|
|
#nextId = 0;
|
|
#initialized = false;
|
|
readonly #getArtifactsDir: () => string | null;
|
|
readonly #parentPrefix: string | undefined;
|
|
|
|
constructor(getArtifactsDir: () => string | null, options?: { parentPrefix?: string }) {
|
|
this.#getArtifactsDir = getArtifactsDir;
|
|
this.#parentPrefix = options?.parentPrefix;
|
|
}
|
|
|
|
/**
|
|
* Scan existing agent output files to find the next available ID.
|
|
* This ensures we don't overwrite outputs when resuming a session.
|
|
*/
|
|
async #ensureInitialized(): Promise<void> {
|
|
if (this.#initialized) return;
|
|
this.#initialized = true;
|
|
|
|
const dir = this.#getArtifactsDir();
|
|
if (!dir) return;
|
|
|
|
let files: string[];
|
|
try {
|
|
files = await fs.readdir(dir);
|
|
} catch {
|
|
return; // Directory doesn't exist yet
|
|
}
|
|
|
|
const pattern = this.#parentPrefix
|
|
? new RegExp(`^${escapeRegExp(this.#parentPrefix)}\\.(\\d+)-.*\\.md$`)
|
|
: /^(\d+)-.*\.md$/;
|
|
|
|
let maxId = -1;
|
|
for (const file of files) {
|
|
const match = file.match(pattern);
|
|
if (match) {
|
|
const id = Number.parseInt(match[1], 10);
|
|
if (id > maxId) maxId = id;
|
|
}
|
|
}
|
|
this.#nextId = maxId + 1;
|
|
}
|
|
|
|
/**
|
|
* Allocate a unique ID with numeric prefix.
|
|
*
|
|
* @param id Requested ID (e.g., "AuthProvider")
|
|
* @returns Unique ID with prefix (e.g., "0-AuthProvider")
|
|
*/
|
|
async allocate(id: string): Promise<string> {
|
|
await this.#ensureInitialized();
|
|
const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
|
|
return `${prefix}${this.#nextId++}-${id}`;
|
|
}
|
|
|
|
/**
|
|
* Allocate unique IDs for a batch of tasks.
|
|
*
|
|
* @param ids Array of requested IDs
|
|
* @returns Array of unique IDs in same order
|
|
*/
|
|
async allocateBatch(ids: string[]): Promise<string[]> {
|
|
await this.#ensureInitialized();
|
|
const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
|
|
return ids.map(id => `${prefix}${this.#nextId++}-${id}`);
|
|
}
|
|
|
|
/**
|
|
* Get the next ID that would be allocated (without allocating).
|
|
*/
|
|
async peekNextIndex(): Promise<number> {
|
|
await this.#ensureInitialized();
|
|
return this.#nextId;
|
|
}
|
|
|
|
/**
|
|
* Reset state (primarily for testing).
|
|
*/
|
|
reset(): void {
|
|
this.#nextId = 0;
|
|
this.#initialized = false;
|
|
}
|
|
}
|