@robota-sdk/agent-framework 3.0.0-beta.79 → 3.0.0-beta.82
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/README.md +192 -43
- package/dist/node/createInteractiveRuntime-BDuatsdk.d.cts +9506 -0
- package/dist/node/createInteractiveRuntime-BDuatsdk.d.cts.map +1 -0
- package/dist/node/createInteractiveRuntime-Bmh2KYP6.d.ts +9506 -0
- package/dist/node/createInteractiveRuntime-Bmh2KYP6.d.ts.map +1 -0
- package/dist/node/index.cjs +29 -3
- package/dist/node/index.d.cts +1646 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1351 -190
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +29 -3
- package/dist/node/index.js.map +1 -1
- package/dist/node/interactive-D5K9ak2P.cjs +122 -0
- package/dist/node/interactive-DnPmdeU0.js +123 -0
- package/dist/node/interactive-DnPmdeU0.js.map +1 -0
- package/dist/node/testing/index.cjs +2 -2
- package/dist/node/testing/index.d.cts +240 -0
- package/dist/node/testing/index.d.cts.map +1 -0
- package/dist/node/testing/index.d.ts +98 -15
- package/dist/node/testing/index.d.ts.map +1 -1
- package/dist/node/testing/index.js +2 -2
- package/dist/node/testing/index.js.map +1 -1
- package/package.json +70 -24
- package/dist/node/index-BeYNnJed.d.ts +0 -2656
- package/dist/node/index-BeYNnJed.d.ts.map +0 -1
- package/dist/node/index-DLFjpsfm.d.ts +0 -2658
- package/dist/node/index-DLFjpsfm.d.ts.map +0 -1
- package/dist/node/interactive-C93XBn4U.js +0 -111
- package/dist/node/interactive-C93XBn4U.js.map +0 -1
- package/dist/node/interactive-D1dVksoo.cjs +0 -110
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["createScriptedProvider"],"sources":["../../../src/testing/scripted-session-harness.ts","../../../src/testing/create-test-interactive-session.ts"],"sourcesContent":["/**\n * TEST-003: framework-level functional session harness.\n *\n * Builds a REAL {@link InteractiveSession} — real agent loop, builtin tools, persistence, events —\n * driven by the deterministic scripted provider (no CLI, no network, no live LLM), in an isolated\n * temp workspace. This is the agent's standard way to prove a framework capability actually works\n * end to end; the CLI is a thin wrapper and must not be the place feature behaviour is verified.\n *\n * The kit is organized for long-term growth: a builder ({@link scriptedSession}), composable\n * drivers ({@link ScriptedSessionHarness.submit}/{@link ScriptedSessionHarness.runGoal}/\n * {@link ScriptedSessionHarness.awaitEvent}), and inspectors (history, session record, files,\n * tool calls, events). New capability drivers/inspectors are added as methods without breaking\n * callers. Exported only via `@robota-sdk/agent-framework/testing`; never import from runtime code.\n */\n\nimport {\n mkdtempSync,\n mkdirSync,\n writeFileSync,\n readFileSync,\n readdirSync,\n rmSync,\n existsSync,\n} from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, join, relative, sep } from 'node:path';\n\nimport {\n createScriptedProvider,\n createReplayProvider,\n createRecordingProvider,\n} from '@robota-sdk/agent-core/testing';\n\nimport { InteractiveSession } from '../interactive/index.js';\nimport { createProjectSessionStore } from '../interactive/index.js';\n\nimport type { ICommandModule } from '../command-api/index.js';\nimport type {\n IAIProvider,\n IUserInteraction,\n TPermissionMode,\n TUniversalMessage,\n} from '@robota-sdk/agent-core';\nimport type { TScriptedTurn } from '@robota-sdk/agent-core/testing';\nimport type {\n ICommandResult,\n IExecutionResult,\n IGoalState,\n IInteractiveSessionEvents,\n IInteractiveSessionRecord,\n IInteractiveSessionStore,\n ITerminalHandoff,\n IToolSummary,\n TInteractiveEventName,\n} from '@robota-sdk/agent-interface-transport';\n\n/** Options for {@link scriptedSession}. Provide exactly one of `turns`, `cassette`, or `record`. */\nexport interface IScriptedSessionOptions {\n /** Scripted assistant turns replayed deterministically through the real loop. */\n turns?: readonly TScriptedTurn[];\n /**\n * CMD-005: answer model-issued questions (AskUserQuestion) programmatically. When set, tool\n * executions receive it as `context.ask`; absent ⇒ the tool reports `unavailable` (headless).\n */\n askHandler?: IUserInteraction['ask'];\n /**\n * Path to a recorded cassette (TEST-005). Replays a real model's captured prompts + tool-use\n * deterministically through the real loop. The workspace path is rewritten/scrubbed automatically.\n */\n cassette?: string;\n /**\n * Record mode (TEST-005): drive the session with a REAL provider and capture every interaction to\n * `toCassette` for later deterministic replay. Used in a one-off keyed record run, not in CI.\n */\n record?: { provider: IAIProvider; toCassette: string };\n /** Seed files written into the workspace before the session starts (workspace-relative paths). */\n files?: Record<string, string>;\n /** Persist sessions to a real store in the workspace (enables resume/record assertions). */\n persistence?: boolean;\n /**\n * Reuse an existing workspace directory instead of a fresh temp one. Required for multi-session\n * resume/fork (the resumed session must read the same store). The harness does not delete a\n * workspace it did not create.\n */\n cwd?: string;\n /** Resume a persisted session by id (multi-session). Requires `persistence` + the same `cwd`. */\n resumeSessionId?: string;\n /** Fork the resumed session into a new id while restoring its context (multi-session). */\n forkSession?: boolean;\n /** Command modules composed into the session (e.g. the `/goal` module). */\n commandModules?: readonly ICommandModule[];\n /** Permission posture. Defaults to `bypassPermissions` so tools run unattended. */\n permissionMode?: TPermissionMode;\n /** Pre-approved tool names. */\n allowedTools?: string[];\n /** Denied tool names (deny wins over allow). */\n deniedTools?: string[];\n /** Skip AGENTS.md/CLAUDE.md and plugin discovery for determinism. Defaults to `true`. */\n bare?: boolean;\n /** Cap on agentic rounds per submit. */\n maxTurns?: number;\n /** Model override (e.g. when recording against a real provider whose model differs). */\n model?: string;\n /** TERM-001: inject a (fake) terminal-handoff capability to exercise the handoff orchestration. */\n terminalHandoff?: ITerminalHandoff;\n}\n\nconst COLLECTED_EVENTS: readonly TInteractiveEventName[] = [\n 'text_delta',\n 'tool_start',\n 'tool_end',\n 'thinking',\n 'complete',\n 'interrupted',\n 'error',\n 'context_update',\n 'goal_event',\n 'turn_source',\n 'user_message',\n];\n\n/**\n * A live, scripted, isolated functional-test session. Construct via {@link scriptedSession};\n * always `await dispose()` in a test teardown.\n */\nexport class ScriptedSessionHarness {\n /** Absolute path of the isolated temp workspace. */\n readonly cwd: string;\n /** The real session under test. */\n readonly session: InteractiveSession;\n /** Message arrays of every provider chat() call, in order, for request assertions. */\n readonly requests: TUniversalMessage[][];\n\n private readonly events = new Map<TInteractiveEventName, unknown[][]>();\n private readonly completions: IExecutionResult[] = [];\n private readonly sessionStore?: IInteractiveSessionStore;\n private readonly ownsWorkspace: boolean;\n private disposed = false;\n\n constructor(options: IScriptedSessionOptions) {\n this.ownsWorkspace = options.cwd === undefined;\n this.cwd = options.cwd ?? mkdtempSync(join(tmpdir(), 'robota-fxn-'));\n if (this.ownsWorkspace) {\n for (const [relPath, content] of Object.entries(options.files ?? {})) {\n const abs = join(this.cwd, relPath);\n mkdirSync(dirname(abs), { recursive: true });\n writeFileSync(abs, content, 'utf8');\n }\n }\n\n const modes = [options.turns, options.cassette, options.record].filter(\n (mode) => mode !== undefined,\n );\n if (modes.length !== 1) {\n throw new Error('scriptedSession requires exactly one of `turns`, `cassette`, or `record`.');\n }\n let base: IAIProvider;\n if (options.cassette) {\n base = createReplayProvider({\n cassettePath: options.cassette,\n scrub: [this.cwd],\n rewriteCwd: this.cwd,\n });\n } else if (options.record) {\n base = createRecordingProvider({\n provider: options.record.provider,\n cassettePath: options.record.toCassette,\n recordCwd: this.cwd,\n });\n } else {\n base = createScriptedProvider(this.substituteWorkspacePath(options.turns ?? [])).provider;\n }\n // Capture every request uniformly (works for both scripted and cassette providers).\n this.requests = [];\n const provider: IAIProvider = {\n ...base,\n chat: (messages, chatOptions) => {\n this.requests.push([...messages]);\n return base.chat(messages, chatOptions);\n },\n };\n\n this.sessionStore = options.persistence ? createProjectSessionStore(this.cwd) : undefined;\n\n this.session = new InteractiveSession({\n cwd: this.cwd,\n provider,\n bare: options.bare ?? true,\n permissionMode: options.permissionMode ?? 'bypassPermissions',\n ...(options.allowedTools ? { allowedTools: options.allowedTools } : {}),\n ...(options.deniedTools ? { deniedTools: options.deniedTools } : {}),\n ...(this.sessionStore ? { sessionStore: this.sessionStore } : {}),\n ...(options.resumeSessionId ? { resumeSessionId: options.resumeSessionId } : {}),\n ...(options.forkSession ? { forkSession: options.forkSession } : {}),\n ...(options.commandModules ? { commandModules: options.commandModules } : {}),\n ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),\n ...(options.model !== undefined ? { model: options.model } : {}),\n ...(options.terminalHandoff ? { terminalHandoff: options.terminalHandoff } : {}),\n ...(options.askHandler ? { askHandler: options.askHandler } : {}),\n });\n\n for (const name of COLLECTED_EVENTS) {\n this.session.on(name, ((...args: unknown[]) => {\n this.record(name, args);\n if (name === 'complete' || name === 'interrupted') {\n this.completions.push(args[0] as IExecutionResult);\n }\n }) as IInteractiveSessionEvents[typeof name]);\n }\n }\n\n /**\n * Replace the `{{cwd}}` placeholder in scripted tool-call args with the isolated workspace path,\n * so a test can reference absolute workspace paths it cannot know until the harness is built\n * (e.g. `{ filePath: '{{cwd}}/out.txt' }` or a Bash `workingDirectory`). Keeps the harness free of\n * global `process.cwd()` mutation.\n */\n private substituteWorkspacePath(turns: readonly TScriptedTurn[]): TScriptedTurn[] {\n return turns.map((turn) => {\n if (!('toolCalls' in turn)) return turn;\n return {\n toolCalls: turn.toolCalls.map((call) => ({\n name: call.name,\n args: JSON.parse(JSON.stringify(call.args).split('{{cwd}}').join(this.cwd)) as Record<\n string,\n unknown\n >,\n })),\n };\n });\n }\n\n private record(name: TInteractiveEventName, args: unknown[]): void {\n const bucket = this.events.get(name) ?? [];\n bucket.push(args);\n this.events.set(name, bucket);\n }\n\n // ── Drivers ────────────────────────────────────────────────\n\n /** Submit a user prompt and resolve with the completed turn result (rejects on `error`). */\n async submit(prompt: string): Promise<IExecutionResult> {\n const settled = this.nextSettledTurn();\n await this.session.submit(prompt);\n return settled;\n }\n\n /**\n * Assign an autonomous goal and resolve with the FINAL stopped goal state once the loop ends\n * (satisfied or a bound). Rejects if the session emits `error` first.\n */\n async runGoal(objective: string, options: { maxIterations?: number } = {}): Promise<IGoalState> {\n const stopped = new Promise<IGoalState>((resolve, reject) => {\n const onGoal = (event: { type: string; goal: IGoalState }): void => {\n if (event.type !== 'goal_stopped') return;\n cleanup();\n resolve(event.goal);\n };\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n const cleanup = (): void => {\n this.session.off('goal_event', onGoal as IInteractiveSessionEvents['goal_event']);\n this.session.off('error', onError);\n };\n this.session.on('goal_event', onGoal as IInteractiveSessionEvents['goal_event']);\n this.session.on('error', onError);\n });\n await this.session.setGoal(\n objective,\n options.maxIterations ? { maxIterations: options.maxIterations } : {},\n );\n return stopped;\n }\n\n /** Run a slash command through the real session command pipeline. */\n command(name: string, args = ''): Promise<ICommandResult | null> {\n return this.session.executeCommand(name, args);\n }\n\n /**\n * FLOW-002: inject a background/scheduled wake (a non-user `agent-wakeup` turn) and resolve once\n * that turn settles. Mirrors a background task completion or a scheduled fire re-entering the\n * agent loop. Coalesces by `sourceTaskId` exactly as the real wake path does.\n *\n * Returns `null` when the wake was a no-op (coalesced because `sourceTaskId` is already in flight,\n * or the session is shutting down) — no turn runs, so callers must not await one. This prevents the\n * driver from hanging until the test timeout when the wake is dropped.\n */\n async wake(instruction: string, sourceTaskId: string): Promise<IExecutionResult | null> {\n const queued = this.session.requestWakeup(instruction, sourceTaskId);\n if (!queued) return null;\n return this.nextSettledTurn();\n }\n\n /** Resolve with the args of the next `event` (optionally matching `predicate`). */\n awaitEvent<E extends TInteractiveEventName>(\n event: E,\n predicate?: (...args: Parameters<IInteractiveSessionEvents[E]>) => boolean,\n ): Promise<Parameters<IInteractiveSessionEvents[E]>> {\n return new Promise((resolve) => {\n const handler = ((...args: unknown[]) => {\n const typed = args as Parameters<IInteractiveSessionEvents[E]>;\n if (predicate && !predicate(...typed)) return;\n this.session.off(event, handler);\n resolve(typed);\n }) as IInteractiveSessionEvents[E];\n this.session.on(event, handler);\n });\n }\n\n private nextSettledTurn(): Promise<IExecutionResult> {\n return new Promise((resolve, reject) => {\n const onComplete = (result: IExecutionResult): void => {\n cleanup();\n resolve(result);\n };\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n const cleanup = (): void => {\n this.session.off('complete', onComplete);\n this.session.off('interrupted', onComplete);\n this.session.off('error', onError);\n };\n this.session.on('complete', onComplete);\n this.session.on('interrupted', onComplete);\n this.session.on('error', onError);\n });\n }\n\n // ── Inspectors ─────────────────────────────────────────────\n\n /** The current conversation messages. */\n history(): TUniversalMessage[] {\n return this.session.getMessages();\n }\n\n /** The persisted session record (requires `persistence: true`), or undefined. */\n sessionRecord(): IInteractiveSessionRecord | undefined {\n if (!this.sessionStore) return undefined;\n return this.sessionStore.load(this.session.getSession().getSessionId());\n }\n\n /**\n * Neutral session-log accessor (INFRA-025): the id + full history in the shape analysis\n * tooling consumes. Usage assertions compose it with `summarizeUsageBySource` from\n * `@robota-sdk/agent-session-analytics` in the TEST — the harness itself carries no\n * analytics dependency.\n */\n sessionLog(): { id: string; history: ReturnType<InteractiveSession['getFullHistory']> } {\n return {\n id: this.session.getSession().getSessionId(),\n history: this.session.getFullHistory(),\n };\n }\n\n /** The real session-log directory the framework writes to (`{cwd}/.robota/logs`). */\n logsDir(): string {\n return join(this.cwd, '.robota', 'logs');\n }\n\n /** Path of the real JSONL transcript the framework writes for this session. */\n transcriptPath(): string {\n return join(this.logsDir(), `${this.session.getSession().getSessionId()}.jsonl`);\n }\n\n /** Raw contents of the real session transcript (`''` if none was written). */\n transcript(): string {\n const path = this.transcriptPath();\n return existsSync(path) ? readFileSync(path, 'utf8') : '';\n }\n\n /**\n * The real session transcript parsed into structured log entries — the durable record the\n * framework itself writes (`{ timestamp, sessionId, event, ... }` per line). Leverages the\n * system's own logging as a verification surface, not just in-memory state.\n */\n logEntries(): Array<Record<string, unknown>> {\n return this.transcript()\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => JSON.parse(line) as Record<string, unknown>);\n }\n\n /** Every tool call the agent made across all completed turns, in order. */\n toolCalls(): IToolSummary[] {\n return this.completions.flatMap((result) => result.toolSummaries);\n }\n\n /** Raw collected args of each emission of `event`. */\n emittedEvents<E extends TInteractiveEventName>(\n event: E,\n ): Array<Parameters<IInteractiveSessionEvents[E]>> {\n return (this.events.get(event) ?? []) as Array<Parameters<IInteractiveSessionEvents[E]>>;\n }\n\n /** Read a workspace file (UTF-8). */\n readFile(relPath: string): string {\n return readFileSync(join(this.cwd, relPath), 'utf8');\n }\n\n /** Whether a workspace file exists. */\n exists(relPath: string): boolean {\n return existsSync(join(this.cwd, relPath));\n }\n\n /** List workspace files (relative paths), excluding the `.robota` session/log dir. */\n files(): string[] {\n const out: string[] = [];\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === '.robota') continue;\n const abs = join(dir, entry.name);\n if (entry.isDirectory()) walk(abs);\n else out.push(relative(this.cwd, abs).split(sep).join('/'));\n }\n };\n walk(this.cwd);\n return out.sort();\n }\n\n // ── Lifecycle ──────────────────────────────────────────────\n\n /** Shut the session down and remove the temp workspace. Idempotent. */\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n await this.session.shutdown({ reason: 'other', message: 'functional test complete' });\n // Only remove a workspace this harness created — a shared/injected `cwd` (resume/fork) is the\n // caller's to clean up. `maxRetries` rides out the occasional ENOTEMPTY race when a just-written\n // file (e.g. a tool wrote into the workspace) is still settling at teardown.\n if (this.ownsWorkspace) {\n rmSync(this.cwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });\n }\n }\n}\n\n/** Build a live, isolated, scripted functional-test session (TEST-003). */\nexport function scriptedSession(options: IScriptedSessionOptions): ScriptedSessionHarness {\n return new ScriptedSessionHarness(options);\n}\n","import type { IInteractiveSession } from '../interactive/i-interactive-session.js';\n\nconst EMPTY_CONTEXT_STATE = {\n usedTokens: 0,\n maxTokens: 200000,\n usedPercentage: 0,\n remainingPercentage: 100,\n};\n\nconst EMPTY_EXECUTION_WORKSPACE = {\n sessionId: 'test-session-id',\n updatedAt: new Date().toISOString(),\n entries: [] as [],\n};\n\nconst EMPTY_GOAL_STATE = {\n id: 'test-goal',\n objective: 'test goal',\n status: 'active' as const,\n iterations: 0,\n maxIterations: 25,\n startedAt: new Date().toISOString(),\n progress: [] as [],\n};\n\nconst EMPTY_BACKGROUND_GROUP = {\n id: '',\n parentSessionId: 'test-session-id',\n waitPolicy: 'wait_all' as const,\n taskIds: [],\n status: 'completed' as const,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n results: [],\n};\n\n/** Creates a stub IInteractiveSession for use in tests. All methods return sensible defaults.\n * Pass overrides to spy on or replace specific methods. */\nexport function createTestInteractiveSession(\n overrides?: Partial<IInteractiveSession>,\n): IInteractiveSession {\n const base: IInteractiveSession = {\n submit: () => Promise.resolve(),\n abort: () => {},\n cancelQueue: () => {},\n shutdown: () => Promise.resolve(),\n isExecuting: () => false,\n getPendingPrompt: () => null,\n getMessages: () => [],\n getContextState: () => ({ ...EMPTY_CONTEXT_STATE }),\n getSession: () => ({ getSessionId: () => 'test-session-id' }),\n getCwd: () => '/workspace',\n executeCommand: () => Promise.resolve(null),\n listCommands: () => [],\n on: () => {},\n off: () => {},\n listBackgroundTasks: () => [],\n getBackgroundTask: () => undefined,\n cancelBackgroundTask: () => Promise.resolve(),\n closeBackgroundTask: () => Promise.resolve(),\n sendBackgroundTask: () => Promise.resolve(),\n readBackgroundTaskLog: () => Promise.resolve({ taskId: '', lines: [] }),\n listBackgroundJobGroups: () => [],\n getBackgroundJobGroup: () => undefined,\n createBackgroundJobGroup: () => ({ ...EMPTY_BACKGROUND_GROUP }),\n waitBackgroundJobGroup: () => Promise.resolve({ ...EMPTY_BACKGROUND_GROUP }),\n getExecutionWorkspaceSnapshot: () => ({ ...EMPTY_EXECUTION_WORKSPACE }),\n listAgentDefinitions: () => [],\n listAgentJobs: () => [],\n spawnAgentJob: () =>\n Promise.resolve({\n id: 'agent_1',\n type: 'general-purpose',\n label: 'general-purpose',\n parentSessionId: 'test-session-id',\n status: 'running' as const,\n mode: 'background' as const,\n depth: 1,\n cwd: '/workspace',\n promptPreview: '',\n updatedAt: new Date().toISOString(),\n }),\n sendAgentJob: () => Promise.resolve(),\n cancelAgentJob: () => Promise.resolve(),\n closeAgentJob: () => Promise.resolve(),\n setGoal: () => Promise.resolve({ ...EMPTY_GOAL_STATE }),\n getGoalState: () => null,\n cancelGoal: () => null,\n ...overrides,\n };\n return base;\n}\n"],"mappings":"8bA2GA,MAAM,EAAqD,CACzD,aACA,aACA,WACA,WACA,WACA,cACA,QACA,iBACA,aACA,cACA,cACF,EAMA,IAAa,EAAb,KAAoC,CAElC,IAEA,QAEA,SAEA,OAA0B,IAAI,IAC9B,YAAmD,CAAC,EACpD,aACA,cACA,SAAmB,GAEnB,YAAY,EAAkC,CAG5C,GAFA,KAAK,cAAgB,EAAQ,MAAQ,IAAA,GACrC,KAAK,IAAM,EAAQ,KAAO,EAAY,EAAK,EAAO,EAAG,aAAa,CAAC,EAC/D,KAAK,cACP,IAAK,GAAM,CAAC,EAAS,KAAY,OAAO,QAAQ,EAAQ,OAAS,CAAC,CAAC,EAAG,CACpE,IAAM,EAAM,EAAK,KAAK,IAAK,CAAO,EAClC,EAAU,EAAQ,CAAG,EAAG,CAAE,UAAW,EAAK,CAAC,EAC3C,EAAc,EAAK,EAAS,MAAM,CACpC,CAMF,GAHc,CAAC,EAAQ,MAAO,EAAQ,SAAU,EAAQ,MAAM,CAAC,CAAC,OAC7D,GAAS,IAAS,IAAA,EAEb,CAAC,CAAC,SAAW,EACnB,MAAU,MAAM,2EAA2E,EAE7F,IAAI,EACJ,AAaE,EAbE,EAAQ,SACH,EAAqB,CAC1B,aAAc,EAAQ,SACtB,MAAO,CAAC,KAAK,GAAG,EAChB,WAAY,KAAK,GACnB,CAAC,EACQ,EAAQ,OACV,EAAwB,CAC7B,SAAU,EAAQ,OAAO,SACzB,aAAc,EAAQ,OAAO,WAC7B,UAAW,KAAK,GAClB,CAAC,EAEMA,EAAuB,KAAK,wBAAwB,EAAQ,OAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAGnF,KAAK,SAAW,CAAC,EACjB,IAAM,EAAwB,CAC5B,GAAG,EACH,MAAO,EAAU,KACf,KAAK,SAAS,KAAK,CAAC,GAAG,CAAQ,CAAC,EACzB,EAAK,KAAK,EAAU,CAAW,EAE1C,EAEA,KAAK,aAAe,EAAQ,YAAc,EAA0B,KAAK,GAAG,EAAI,IAAA,GAEhF,KAAK,QAAU,IAAI,EAAmB,CACpC,IAAK,KAAK,IACV,WACA,KAAM,EAAQ,MAAQ,GACtB,eAAgB,EAAQ,gBAAkB,oBAC1C,GAAI,EAAQ,aAAe,CAAE,aAAc,EAAQ,YAAa,EAAI,CAAC,EACrE,GAAI,EAAQ,YAAc,CAAE,YAAa,EAAQ,WAAY,EAAI,CAAC,EAClE,GAAI,KAAK,aAAe,CAAE,aAAc,KAAK,YAAa,EAAI,CAAC,EAC/D,GAAI,EAAQ,gBAAkB,CAAE,gBAAiB,EAAQ,eAAgB,EAAI,CAAC,EAC9E,GAAI,EAAQ,YAAc,CAAE,YAAa,EAAQ,WAAY,EAAI,CAAC,EAClE,GAAI,EAAQ,eAAiB,CAAE,eAAgB,EAAQ,cAAe,EAAI,CAAC,EAC3E,GAAI,EAAQ,WAAa,IAAA,GAA6C,CAAC,EAAlC,CAAE,SAAU,EAAQ,QAAS,EAClE,GAAI,EAAQ,QAAU,IAAA,GAAuC,CAAC,EAA5B,CAAE,MAAO,EAAQ,KAAM,EACzD,GAAI,EAAQ,gBAAkB,CAAE,gBAAiB,EAAQ,eAAgB,EAAI,CAAC,EAC9E,GAAI,EAAQ,WAAa,CAAE,WAAY,EAAQ,UAAW,EAAI,CAAC,CACjE,CAAC,EAED,IAAK,IAAM,KAAQ,EACjB,KAAK,QAAQ,GAAG,IAAQ,GAAG,IAAoB,CAC7C,KAAK,OAAO,EAAM,CAAI,GAClB,IAAS,YAAc,IAAS,gBAClC,KAAK,YAAY,KAAK,EAAK,EAAsB,CAErD,EAA4C,CAEhD,CAQA,wBAAgC,EAAkD,CAChF,OAAO,EAAM,IAAK,GACV,cAAe,EACd,CACL,UAAW,EAAK,UAAU,IAAK,IAAU,CACvC,KAAM,EAAK,KACX,KAAM,KAAK,MAAM,KAAK,UAAU,EAAK,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAI5E,EAAE,CACJ,EATmC,CAUpC,CACH,CAEA,OAAe,EAA6B,EAAuB,CACjE,IAAM,EAAS,KAAK,OAAO,IAAI,CAAI,GAAK,CAAC,EACzC,EAAO,KAAK,CAAI,EAChB,KAAK,OAAO,IAAI,EAAM,CAAM,CAC9B,CAKA,MAAM,OAAO,EAA2C,CACtD,IAAM,EAAU,KAAK,gBAAgB,EAErC,OADA,MAAM,KAAK,QAAQ,OAAO,CAAM,EACzB,CACT,CAMA,MAAM,QAAQ,EAAmB,EAAsC,CAAC,EAAwB,CAC9F,IAAM,EAAU,IAAI,SAAqB,EAAS,IAAW,CAC3D,IAAM,EAAU,GAAoD,CAC9D,EAAM,OAAS,iBACnB,EAAQ,EACR,EAAQ,EAAM,IAAI,EACpB,EACM,EAAW,GAAuB,CACtC,EAAQ,EACR,EAAO,CAAK,CACd,EACM,MAAsB,CAC1B,KAAK,QAAQ,IAAI,aAAc,CAAiD,EAChF,KAAK,QAAQ,IAAI,QAAS,CAAO,CACnC,EACA,KAAK,QAAQ,GAAG,aAAc,CAAiD,EAC/E,KAAK,QAAQ,GAAG,QAAS,CAAO,CAClC,CAAC,EAKD,OAJA,MAAM,KAAK,QAAQ,QACjB,EACA,EAAQ,cAAgB,CAAE,cAAe,EAAQ,aAAc,EAAI,CAAC,CACtE,EACO,CACT,CAGA,QAAQ,EAAc,EAAO,GAAoC,CAC/D,OAAO,KAAK,QAAQ,eAAe,EAAM,CAAI,CAC/C,CAWA,MAAM,KAAK,EAAqB,EAAwD,CAGtF,OAFe,KAAK,QAAQ,cAAc,EAAa,CAC7C,EACH,KAAK,gBAAgB,EADR,IAEtB,CAGA,WACE,EACA,EACmD,CACnD,OAAO,IAAI,QAAS,GAAY,CAC9B,IAAM,IAAY,GAAG,IAAoB,CACvC,IAAM,EAAQ,EACV,GAAa,CAAC,EAAU,GAAG,CAAK,IACpC,KAAK,QAAQ,IAAI,EAAO,CAAO,EAC/B,EAAQ,CAAK,EACf,GACA,KAAK,QAAQ,GAAG,EAAO,CAAO,CAChC,CAAC,CACH,CAEA,iBAAqD,CACnD,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,EAAc,GAAmC,CACrD,EAAQ,EACR,EAAQ,CAAM,CAChB,EACM,EAAW,GAAuB,CACtC,EAAQ,EACR,EAAO,CAAK,CACd,EACM,MAAsB,CAC1B,KAAK,QAAQ,IAAI,WAAY,CAAU,EACvC,KAAK,QAAQ,IAAI,cAAe,CAAU,EAC1C,KAAK,QAAQ,IAAI,QAAS,CAAO,CACnC,EACA,KAAK,QAAQ,GAAG,WAAY,CAAU,EACtC,KAAK,QAAQ,GAAG,cAAe,CAAU,EACzC,KAAK,QAAQ,GAAG,QAAS,CAAO,CAClC,CAAC,CACH,CAKA,SAA+B,CAC7B,OAAO,KAAK,QAAQ,YAAY,CAClC,CAGA,eAAuD,CAChD,QAAK,aACV,OAAO,KAAK,aAAa,KAAK,KAAK,QAAQ,WAAW,CAAC,CAAC,aAAa,CAAC,CACxE,CAQA,YAAwF,CACtF,MAAO,CACL,GAAI,KAAK,QAAQ,WAAW,CAAC,CAAC,aAAa,EAC3C,QAAS,KAAK,QAAQ,eAAe,CACvC,CACF,CAGA,SAAkB,CAChB,OAAO,EAAK,KAAK,IAAK,UAAW,MAAM,CACzC,CAGA,gBAAyB,CACvB,OAAO,EAAK,KAAK,QAAQ,EAAG,GAAG,KAAK,QAAQ,WAAW,CAAC,CAAC,aAAa,EAAE,OAAO,CACjF,CAGA,YAAqB,CACnB,IAAM,EAAO,KAAK,eAAe,EACjC,OAAO,EAAW,CAAI,EAAI,EAAa,EAAM,MAAM,EAAI,EACzD,CAOA,YAA6C,CAC3C,OAAO,KAAK,WAAW,CAAC,CACrB,MAAM;CAAI,CAAC,CACX,OAAQ,GAAS,EAAK,KAAK,CAAC,CAAC,OAAS,CAAC,CAAC,CACxC,IAAK,GAAS,KAAK,MAAM,CAAI,CAA4B,CAC9D,CAGA,WAA4B,CAC1B,OAAO,KAAK,YAAY,QAAS,GAAW,EAAO,aAAa,CAClE,CAGA,cACE,EACiD,CACjD,OAAQ,KAAK,OAAO,IAAI,CAAK,GAAK,CAAC,CACrC,CAGA,SAAS,EAAyB,CAChC,OAAO,EAAa,EAAK,KAAK,IAAK,CAAO,EAAG,MAAM,CACrD,CAGA,OAAO,EAA0B,CAC/B,OAAO,EAAW,EAAK,KAAK,IAAK,CAAO,CAAC,CAC3C,CAGA,OAAkB,CAChB,IAAM,EAAgB,CAAC,EACjB,EAAQ,GAAsB,CAClC,IAAK,IAAM,KAAS,EAAY,EAAK,CAAE,cAAe,EAAK,CAAC,EAAG,CAC7D,GAAI,EAAM,OAAS,UAAW,SAC9B,IAAM,EAAM,EAAK,EAAK,EAAM,IAAI,EAC5B,EAAM,YAAY,EAAG,EAAK,CAAG,EAC5B,EAAI,KAAK,EAAS,KAAK,IAAK,CAAG,CAAC,CAAC,MAAM,CAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAC5D,CACF,EAEA,OADA,EAAK,KAAK,GAAG,EACN,EAAI,KAAK,CAClB,CAKA,MAAM,SAAyB,CACzB,KAAK,WACT,KAAK,SAAW,GAChB,MAAM,KAAK,QAAQ,SAAS,CAAE,OAAQ,QAAS,QAAS,0BAA2B,CAAC,EAIhF,KAAK,eACP,EAAO,KAAK,IAAK,CAAE,UAAW,GAAM,MAAO,GAAM,WAAY,EAAG,WAAY,EAAG,CAAC,EAEpF,CACF,EAGA,SAAgB,EAAgB,EAA0D,CACxF,OAAO,IAAI,EAAuB,CAAO,CAC3C,CCzbA,MAAM,EAAsB,CAC1B,WAAY,EACZ,UAAW,IACX,eAAgB,EAChB,oBAAqB,GACvB,EAEM,EAA4B,CAChC,UAAW,kBACX,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,QAAS,CAAC,CACZ,EAEM,EAAmB,CACvB,GAAI,YACJ,UAAW,YACX,OAAQ,SACR,WAAY,EACZ,cAAe,GACf,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,SAAU,CAAC,CACb,EAEM,EAAyB,CAC7B,GAAI,GACJ,gBAAiB,kBACjB,WAAY,WACZ,QAAS,CAAC,EACV,OAAQ,YACR,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,QAAS,CAAC,CACZ,EAIA,SAAgB,EACd,EACqB,CAkDrB,MAAO,CAhDL,WAAc,QAAQ,QAAQ,EAC9B,UAAa,CAAC,EACd,gBAAmB,CAAC,EACpB,aAAgB,QAAQ,QAAQ,EAChC,gBAAmB,GACnB,qBAAwB,KACxB,gBAAmB,CAAC,EACpB,qBAAwB,CAAE,GAAG,CAAoB,GACjD,gBAAmB,CAAE,iBAAoB,iBAAkB,GAC3D,WAAc,aACd,mBAAsB,QAAQ,QAAQ,IAAI,EAC1C,iBAAoB,CAAC,EACrB,OAAU,CAAC,EACX,QAAW,CAAC,EACZ,wBAA2B,CAAC,EAC5B,sBAAyB,IAAA,GACzB,yBAA4B,QAAQ,QAAQ,EAC5C,wBAA2B,QAAQ,QAAQ,EAC3C,uBAA0B,QAAQ,QAAQ,EAC1C,0BAA6B,QAAQ,QAAQ,CAAE,OAAQ,GAAI,MAAO,CAAC,CAAE,CAAC,EACtE,4BAA+B,CAAC,EAChC,0BAA6B,IAAA,GAC7B,8BAAiC,CAAE,GAAG,CAAuB,GAC7D,2BAA8B,QAAQ,QAAQ,CAAE,GAAG,CAAuB,CAAC,EAC3E,mCAAsC,CAAE,GAAG,CAA0B,GACrE,yBAA4B,CAAC,EAC7B,kBAAqB,CAAC,EACtB,kBACE,QAAQ,QAAQ,CACd,GAAI,UACJ,KAAM,kBACN,MAAO,kBACP,gBAAiB,kBACjB,OAAQ,UACR,KAAM,aACN,MAAO,EACP,IAAK,aACL,cAAe,GACf,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,CACpC,CAAC,EACH,iBAAoB,QAAQ,QAAQ,EACpC,mBAAsB,QAAQ,QAAQ,EACtC,kBAAqB,QAAQ,QAAQ,EACrC,YAAe,QAAQ,QAAQ,CAAE,GAAG,CAAiB,CAAC,EACtD,iBAAoB,KACpB,eAAkB,KAClB,GAAG,CAEK,CACZ"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/testing/harness-peer-driver.ts","../../../src/testing/harness-workspace-inspectors.ts","../../../src/testing/scripted-session-harness.ts","../../../src/testing/double-constants.ts","../../../src/testing/double-overrides.ts","../../../src/testing/agent-job-host-double.ts","../../../src/testing/command-host-double.ts","../../../src/testing/interactive-runtime.ts"],"sourcesContent":["/**\n * PEER-002 (#1809): how the harness addresses a turn as coming from another session.\n *\n * A separate file because the reasoning is longer than the code, and because the harness is at its\n * size ratchet where the rule is to split rather than extend.\n */\n\nimport type { ISubmitOptions } from '@robota-sdk/agent-interface-session';\n\n/**\n * Submission options for a message that arrived from a peer session.\n *\n * The driver id is DERIVED from the peer's session id rather than passed through from the peer's\n * own `IPeerOrigin.driverId`. That field is optional and peer-supplied, and a name the transcript's\n * reader trusts must not be chosen by the party being named — a peer that could pick its own\n * display name could pick the operator's.\n *\n * Admission is not decided here. Whether this peer may speak to this session at all is settled\n * before anything reaches a submission, by whoever holds the admission port; a test harness that\n * also gated admission would be able to pass a test the product would fail.\n */\nexport function peerTurnOptions(peerSessionId: string): ISubmitOptions {\n return { turnSource: 'peer', driverId: `peer:${peerSessionId}` };\n}\n","/**\n * What the harness reads off DISK, as opposed to out of the session.\n *\n * Split from `scripted-session-harness.ts`, which had reached its size ratchet where the rule is to\n * split rather than extend. The boundary is real rather than convenient: everything here answers a\n * question about the workspace — what the framework WROTE — while the harness's other inspectors\n * answer questions about live session state. A test asserting on the transcript is using the\n * system's own durable record as its verification surface, and that is a different kind of evidence\n * from reading an in-memory field.\n *\n * Free functions taking the paths they need, so none of them can reach for session state and\n * quietly become the other kind.\n */\n\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, relative, sep } from 'node:path';\n\n/** The real session-log directory the framework writes to, under the workspace. */\nexport function logsDirOf(cwd: string): string {\n return join(cwd, '.robota', 'logs');\n}\n\n/** Path of the real JSONL transcript the framework writes for one session. */\nexport function transcriptPathOf(cwd: string, sessionId: string): string {\n return join(logsDirOf(cwd), `${sessionId}.jsonl`);\n}\n\n/**\n * Raw contents of the real session transcript.\n *\n * Returns `''` when no transcript exists rather than throwing: \"the framework wrote nothing\" is a\n * legitimate outcome a test may want to assert, and it is not the same as a broken read.\n */\nexport function transcriptOf(cwd: string, sessionId: string): string {\n const path = transcriptPathOf(cwd, sessionId);\n return existsSync(path) ? readFileSync(path, 'utf8') : '';\n}\n\n/**\n * The transcript parsed into structured log entries — the durable record the framework itself\n * writes (`{ timestamp, sessionId, event, … }` per line).\n */\nexport function logEntriesOf(cwd: string, sessionId: string): Array<Record<string, unknown>> {\n return transcriptOf(cwd, sessionId)\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => JSON.parse(line) as Record<string, unknown>);\n}\n\n/** Read a workspace file by workspace-relative path. */\nexport function readWorkspaceFile(cwd: string, relPath: string): string {\n return readFileSync(join(cwd, relPath), 'utf8');\n}\n\n/** Does a workspace-relative path exist? */\nexport function workspaceFileExists(cwd: string, relPath: string): boolean {\n return existsSync(join(cwd, relPath));\n}\n\n/**\n * Every file in the workspace, workspace-relative, excluding the framework's own state directory —\n * a test asserting \"what did the agent create\" means the agent's files, and the session log is not\n * one of them.\n *\n * Paths are normalised to forward slashes. A test asserting `'src/index.ts'` should not have to\n * care which platform ran it, and the alternative is an assertion that passes on CI and fails on a\n * developer's Windows machine for a reason that has nothing to do with the behaviour under test.\n */\nexport function workspaceFiles(cwd: string): string[] {\n const out: string[] = [];\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === '.robota') continue;\n const abs = join(dir, entry.name);\n if (entry.isDirectory()) walk(abs);\n else out.push(relative(cwd, abs).split(sep).join('/'));\n }\n };\n walk(cwd);\n return out.sort();\n}\n","/**\n * TEST-003: framework-level functional session harness.\n *\n * Builds a REAL {@link InteractiveSession} — real agent loop, builtin tools, persistence, events —\n * driven by the deterministic scripted provider (no CLI, no network, no live LLM), in an isolated\n * temp workspace. This is the agent's standard way to prove a framework capability actually works\n * end to end; the CLI is a thin wrapper and must not be the place feature behaviour is verified.\n *\n * The kit is organized for long-term growth: a builder ({@link scriptedSession}), composable\n * drivers ({@link ScriptedSessionHarness.submit}/{@link ScriptedSessionHarness.runGoal}/\n * {@link ScriptedSessionHarness.awaitEvent}), and inspectors (history, session record, files,\n * tool calls, events). New capability drivers/inspectors are added as methods without breaking\n * callers. Exported only via `@robota-sdk/agent-framework/testing`; never import from runtime code.\n */\n\nimport { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport {\n createScriptedProvider,\n createReplayProvider,\n createRecordingProvider,\n} from '@robota-sdk/agent-core/testing';\nimport { createDefaultBackgroundTaskRunners } from '@robota-sdk/agent-executor';\nimport { NodeSessionLogSink, NodeSessionStore } from '@robota-sdk/agent-session';\nimport type { IContributionSource } from '../contributions/contribution-source.js';\nimport type { ISkillRootDescriptor } from '../commands/skill-source.js';\n\nimport { peerTurnOptions } from './harness-peer-driver.js';\nimport {\n logEntriesOf,\n logsDirOf,\n readWorkspaceFile,\n transcriptOf,\n transcriptPathOf,\n workspaceFileExists,\n workspaceFiles,\n} from './harness-workspace-inspectors.js';\nimport { InteractiveSession } from '../interactive/index.js';\nimport { createNodeHostSettingsSource } from '../config/node-host-settings-source.js';\n\nimport type { IToolCallHandoffPolicy } from '../assembly/index.js';\nimport type { ICommandModule } from '../command-api/index.js';\nimport type { TWorkspaceProjectAccess } from '../workspace-trust/index.js';\nimport type {\n IAIProvider,\n IToolWithEventService,\n IUserInteraction,\n THooksConfig,\n TPermissionMode,\n TUniversalMessage,\n} from '@robota-sdk/agent-core';\nimport type { TScriptedTurn } from '@robota-sdk/agent-core/testing';\nimport type { IBackgroundTaskRunner } from '@robota-sdk/agent-executor';\nimport type { ISandboxClient } from '@robota-sdk/agent-tools';\nimport type { ICommandResult } from '@robota-sdk/agent-interface-command';\nimport type {\n IExecutionResult,\n IGoalState,\n IInteractiveSessionEvents,\n IInteractiveSessionRecord,\n IInteractiveSessionStore,\n ITerminalHandoff,\n IToolSummary,\n TInteractiveEventName,\n} from '@robota-sdk/agent-interface-session';\n\n/** Options for {@link scriptedSession}. Provide exactly one of `turns`, `cassette`, or `record`. */\nexport interface IScriptedSessionOptions {\n /** Scripted assistant turns replayed deterministically through the real loop. */\n turns?: readonly TScriptedTurn[];\n /**\n * CMD-005: answer model-issued questions (AskUserQuestion) programmatically. When set, the harness\n * subscribes to the session's `ask_request` event and answers via `resolveAsk` (REMOTE-007). Absent ⇒\n * nothing subscribes, so a model-issued question fails closed (each question `cancelled`) — the\n * headless no-human path.\n */\n askHandler?: IUserInteraction['ask'];\n /**\n * Path to a recorded cassette (TEST-005). Replays a real model's captured prompts + tool-use\n * deterministically through the real loop. The workspace path is rewritten/scrubbed automatically.\n */\n cassette?: string;\n /**\n * Record mode (TEST-005): drive the session with a REAL provider and capture every interaction to\n * `toCassette` for later deterministic replay. Used in a one-off keyed record run, not in CI.\n */\n record?: { provider: IAIProvider; toCassette: string };\n /** Awaited before every provider call, so a test can hold a turn open while it observes. */\n beforeProviderCall?: () => Promise<void>;\n /** Seed files written into the workspace before the session starts (workspace-relative paths). */\n files?: Record<string, string>;\n /** Persist sessions to a real store in the workspace (enables resume/record assertions). */\n persistence?: boolean;\n /**\n * Reuse an existing workspace directory instead of a fresh temp one. Required for multi-session\n * resume/fork (the resumed session must read the same store). The harness does not delete a\n * workspace it did not create.\n */\n cwd?: string;\n /** Explicit host-issued project authority for contribution-discovery fixtures. */\n projectAccess?: TWorkspaceProjectAccess;\n /** Explicit host contribution sources used by skill discovery fixtures. */\n contributionSources?: readonly IContributionSource[];\n /** Ordered host-owned skill roots used by skill discovery fixtures. */\n skillRoots?: readonly ISkillRootDescriptor[];\n resolveDefaultLoopPrompt?: () => string;\n /** Ordered host-owned agent-definition roots for discovery tests. */\n agentDefinitionRoots?: readonly string[];\n /** Resume a persisted session by id (multi-session). Requires `persistence` + the same `cwd`. */\n resumeSessionId?: string;\n /** Fork the resumed session into a new id while restoring its context (multi-session). */\n forkSession?: boolean;\n /** Issue #3081: the session is the target of a `/cd` from this directory. */\n workspaceMovedFrom?: string;\n /** Command modules composed into the session (e.g. the `/goal` module). */\n commandModules?: readonly ICommandModule[];\n /** Enable the real background-task runners for schedule/monitor functional tests. */\n backgroundTasks?: boolean;\n /** Permission posture. Defaults to `bypassPermissions` so tools run unattended. */\n permissionMode?: TPermissionMode;\n /** Pre-approved tool names. */\n allowedTools?: string[];\n /** Denied tool names (deny wins over allow). */\n deniedTools?: string[];\n /**\n * Permission PATTERNS (`Bash(rm *)`), as a user's settings file would carry them — read through a\n * host settings source, the route the product uses. Tool-name lists are `allowedTools`/`deniedTools`.\n */\n permissions?: { allow?: string[]; deny?: string[]; ask?: string[] };\n /** Hook configuration, written into the same user settings file as `permissions`. */\n hooks?: THooksConfig;\n /** A sandbox client, as a product that confines commands supplies one. */\n sandboxClient?: ISandboxClient;\n /** Skip AGENTS.md/CLAUDE.md and plugin discovery for determinism. Defaults to `true`. */\n bare?: boolean;\n /** Cap on agentic rounds per submit. */\n maxTurns?: number;\n /** Model override (e.g. when recording against a real provider whose model differs). */\n model?: string;\n /** TERM-001: inject a (fake) terminal-handoff capability to exercise the handoff orchestration. */\n terminalHandoff?: ITerminalHandoff;\n /** Additional tools registered alongside the default CLI tools (e.g. a fake slow MCP-like tool). */\n additionalTools?: IToolWithEventService[];\n /** Runtime-composed background task runners (e.g. the `tool-invocation` runner, MCP-004). */\n backgroundTaskRunners?: IBackgroundTaskRunner[];\n /** MCP-004 §S3: hand a main-turn tool call exceeding its threshold to a background task. */\n toolCallHandoff?: IToolCallHandoffPolicy;\n}\n\nconst COLLECTED_EVENTS: readonly TInteractiveEventName[] = [\n 'text_delta',\n 'tool_start',\n 'tool_end',\n 'thinking',\n 'complete',\n 'interrupted',\n 'error',\n 'context_update',\n 'goal_event',\n 'turn_source',\n 'user_message',\n // MCP-004 §S3 (TC-24): the tracker's forwarded background-task lifecycle events.\n 'background_task_event',\n];\n\n/**\n * A live, scripted, isolated functional-test session. Construct via {@link scriptedSession};\n * always `await dispose()` in a test teardown.\n */\nexport class ScriptedSessionHarness {\n /** Absolute path of the isolated temp workspace. */\n readonly cwd: string;\n /** The real session under test. */\n readonly session: InteractiveSession;\n /** Message arrays of every provider chat() call, in order, for request assertions. */\n readonly requests: TUniversalMessage[][];\n\n private readonly events = new Map<TInteractiveEventName, unknown[][]>();\n private readonly completions: IExecutionResult[] = [];\n private readonly sessionStore?: IInteractiveSessionStore;\n private readonly ownsWorkspace: boolean;\n private disposed = false;\n\n constructor(options: IScriptedSessionOptions) {\n this.ownsWorkspace = options.cwd === undefined;\n this.cwd = options.cwd ?? realpathSync(mkdtempSync(join(tmpdir(), 'robota-fxn-')));\n if (this.ownsWorkspace) {\n for (const [relPath, content] of Object.entries(options.files ?? {})) {\n const abs = join(this.cwd, relPath);\n mkdirSync(dirname(abs), { recursive: true });\n writeFileSync(abs, content, 'utf8');\n }\n }\n\n const modes = [options.turns, options.cassette, options.record].filter(\n (mode) => mode !== undefined,\n );\n if (modes.length !== 1) {\n throw new Error('scriptedSession requires exactly one of `turns`, `cassette`, or `record`.');\n }\n let base: IAIProvider;\n if (options.cassette) {\n base = createReplayProvider({\n cassettePath: options.cassette,\n scrub: [this.cwd],\n rewriteCwd: this.cwd,\n });\n } else if (options.record) {\n base = createRecordingProvider({\n provider: options.record.provider,\n cassettePath: options.record.toCassette,\n recordCwd: this.cwd,\n });\n } else {\n base = createScriptedProvider(this.substituteWorkspacePath(options.turns ?? [])).provider;\n }\n // Capture every scripted or cassette request uniformly.\n this.requests = [];\n const provider: IAIProvider = {\n ...base,\n chat: async (messages, chatOptions) => {\n this.requests.push([...messages]);\n await options.beforeProviderCall?.();\n return base.chat(messages, chatOptions);\n },\n };\n const persistenceDir = join(this.cwd, '.robota', 'sessions');\n this.sessionStore = options.persistence ? new NodeSessionStore(persistenceDir) : undefined;\n let userSettingsPath: string | undefined;\n if (options.permissions !== undefined || options.hooks !== undefined) {\n userSettingsPath = join(this.cwd, '.robota-test-user-settings.json');\n const settings = {\n ...(options.permissions !== undefined ? { permissions: options.permissions } : {}),\n ...(options.hooks !== undefined ? { hooks: options.hooks } : {}),\n };\n writeFileSync(userSettingsPath, JSON.stringify(settings), 'utf8');\n }\n this.session = new InteractiveSession({\n cwd: this.cwd,\n provider,\n ...(userSettingsPath !== undefined\n ? { userSettingsSources: [createNodeHostSettingsSource('user', userSettingsPath)] }\n : {}),\n ...(options.projectAccess ? { projectAccess: options.projectAccess } : {}),\n ...(options.sandboxClient ? { sandboxClient: options.sandboxClient } : {}),\n ...(options.contributionSources !== undefined\n ? { contributionSources: options.contributionSources }\n : {}),\n ...(options.skillRoots !== undefined ? { skillRoots: options.skillRoots } : {}),\n ...(options.resolveDefaultLoopPrompt\n ? { resolveDefaultLoopPrompt: options.resolveDefaultLoopPrompt }\n : {}),\n ...(options.agentDefinitionRoots !== undefined\n ? { agentDefinitionRoots: options.agentDefinitionRoots }\n : {}),\n bare: options.bare ?? true,\n permissionMode: options.permissionMode ?? 'bypassPermissions',\n ...(options.allowedTools ? { allowedTools: options.allowedTools } : {}),\n ...(options.deniedTools ? { deniedTools: options.deniedTools } : {}),\n ...(this.sessionStore ? { sessionStore: this.sessionStore } : {}),\n sessionLogSink: new NodeSessionLogSink(join(this.cwd, '.robota', 'logs')),\n ...(options.resumeSessionId ? { resumeSessionId: options.resumeSessionId } : {}),\n ...(options.forkSession ? { forkSession: options.forkSession } : {}),\n ...(options.workspaceMovedFrom !== undefined\n ? { workspaceMovedFrom: options.workspaceMovedFrom }\n : {}),\n ...(options.commandModules ? { commandModules: options.commandModules } : {}),\n ...(options.backgroundTasks\n ? { backgroundTaskRunners: createDefaultBackgroundTaskRunners() }\n : {}),\n ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),\n ...(options.model !== undefined ? { model: options.model } : {}),\n ...(options.terminalHandoff ? { terminalHandoff: options.terminalHandoff } : {}),\n ...(options.additionalTools ? { additionalTools: options.additionalTools } : {}),\n ...(options.backgroundTaskRunners\n ? { backgroundTaskRunners: options.backgroundTaskRunners }\n : {}),\n ...(options.toolCallHandoff ? { toolCallHandoff: options.toolCallHandoff } : {}),\n });\n\n for (const name of COLLECTED_EVENTS) {\n this.session.on(name, ((...args: unknown[]) => {\n this.record(name, args);\n if (name === 'complete' || name === 'interrupted') {\n this.completions.push(args[0] as IExecutionResult);\n }\n }) as IInteractiveSessionEvents[typeof name]);\n }\n\n // REMOTE-007: the harness plays the interactive user by SUBSCRIBING to the transport-neutral\n // `ask_request` event (the same seam a TUI/remote surface uses) and answering via `resolveAsk`,\n // rather than injecting a one-surface `askHandler`. With no `askHandler`, nothing subscribes, so a\n // model-issued question fails closed (each question `cancelled`) — the headless no-human path.\n if (options.askHandler) {\n const askHandler = options.askHandler;\n this.session.on('ask_request', ({ id, request }) => {\n void Promise.resolve(askHandler(request))\n .then((response) => this.session.resolveAsk(id, response))\n .catch(() => this.session.resolveAsk(id, { type: 'cancelled' }));\n });\n }\n }\n\n /**\n * Replace the `{{cwd}}` placeholder in scripted tool-call args with the isolated workspace path,\n * so a test can reference absolute workspace paths it cannot know until the harness is built\n * (e.g. `{ filePath: '{{cwd}}/out.txt' }` or a Bash `workingDirectory`). Keeps the harness free of\n * global `process.cwd()` mutation.\n */\n private substituteWorkspacePath(turns: readonly TScriptedTurn[]): TScriptedTurn[] {\n return turns.map((turn) => {\n if (!('toolCalls' in turn)) return turn;\n return {\n toolCalls: turn.toolCalls.map((call) => ({\n name: call.name,\n args: JSON.parse(JSON.stringify(call.args).split('{{cwd}}').join(this.cwd)) as Record<\n string,\n unknown\n >,\n })),\n };\n });\n }\n\n private record(name: TInteractiveEventName, args: unknown[]): void {\n const bucket = this.events.get(name) ?? [];\n bucket.push(args);\n this.events.set(name, bucket);\n }\n\n // ── Drivers ────────────────────────────────────────────────\n\n /** Submit a user prompt and resolve with the completed turn result (rejects on `error`). */\n async submit(prompt: string): Promise<IExecutionResult> {\n const settled = this.nextSettledTurn();\n await this.session.submit(prompt);\n return settled;\n }\n\n /**\n * Assign an autonomous goal and resolve with the FINAL stopped goal state once the loop ends\n * (satisfied or a bound). Rejects if the session emits `error` first.\n */\n async runGoal(objective: string, options: { maxIterations?: number } = {}): Promise<IGoalState> {\n const stopped = new Promise<IGoalState>((resolve, reject) => {\n const onGoal = (event: { type: string; goal: IGoalState }): void => {\n if (event.type !== 'goal_stopped') return;\n cleanup();\n resolve(event.goal);\n };\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n const cleanup = (): void => {\n this.session.off('goal_event', onGoal as IInteractiveSessionEvents['goal_event']);\n this.session.off('error', onError);\n };\n this.session.on('goal_event', onGoal as IInteractiveSessionEvents['goal_event']);\n this.session.on('error', onError);\n });\n await this.session.setGoal(\n objective,\n options.maxIterations ? { maxIterations: options.maxIterations } : {},\n );\n return stopped;\n }\n\n /** Run a slash command through the real session command pipeline. */\n command(name: string, args = ''): Promise<ICommandResult | null> {\n return this.session.executeCommand(name, args);\n }\n\n /**\n * FLOW-002: inject a background/scheduled wake (a non-user `agent-wakeup` turn) and resolve once\n * that turn settles. Mirrors a background task completion or a scheduled fire re-entering the\n * agent loop. Coalesces by `sourceTaskId` exactly as the real wake path does.\n *\n * Returns `null` when the wake was a no-op (coalesced because `sourceTaskId` is already in flight,\n * or the session is shutting down) — no turn runs, so callers must not await one. This prevents the\n * driver from hanging until the test timeout when the wake is dropped.\n */\n async wake(instruction: string, sourceTaskId: string): Promise<IExecutionResult | null> {\n const queued = this.session.requestWakeup(instruction, sourceTaskId);\n if (!queued) return null;\n return this.nextSettledTurn();\n }\n\n /** PEER-002 (#1809): deliver a message from ANOTHER SESSION; see `peerTurnOptions`. */\n async submitPeer(text: string, peerSessionId: string): Promise<IExecutionResult> {\n const settled = this.nextSettledTurn();\n await this.session.submit(text, undefined, undefined, peerTurnOptions(peerSessionId));\n return settled;\n }\n\n /** Resolve with the args of the next `event` (optionally matching `predicate`). */\n awaitEvent<E extends TInteractiveEventName>(\n event: E,\n predicate?: (...args: Parameters<IInteractiveSessionEvents[E]>) => boolean,\n ): Promise<Parameters<IInteractiveSessionEvents[E]>> {\n return new Promise((resolve) => {\n const handler = ((...args: unknown[]) => {\n const typed = args as Parameters<IInteractiveSessionEvents[E]>;\n if (predicate && !predicate(...typed)) return;\n this.session.off(event, handler);\n resolve(typed);\n }) as IInteractiveSessionEvents[E];\n this.session.on(event, handler);\n });\n }\n\n private nextSettledTurn(): Promise<IExecutionResult> {\n return new Promise((resolve, reject) => {\n const onComplete = (result: IExecutionResult): void => {\n cleanup();\n resolve(result);\n };\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n const cleanup = (): void => {\n this.session.off('complete', onComplete);\n this.session.off('interrupted', onComplete);\n this.session.off('error', onError);\n };\n this.session.on('complete', onComplete);\n this.session.on('interrupted', onComplete);\n this.session.on('error', onError);\n });\n }\n\n // ── Inspectors ─────────────────────────────────────────────\n\n /** The current conversation messages. */\n history(): TUniversalMessage[] {\n return this.session.getMessages();\n }\n\n /** The persisted session record (requires `persistence: true`), or undefined. */\n sessionRecord(): IInteractiveSessionRecord | undefined {\n if (!this.sessionStore) return undefined;\n const o = this.sessionStore.load(this.session.getSession().getSessionId());\n return o.status === 'valid' ? o.record : undefined;\n }\n\n /**\n * Neutral session-log accessor (INFRA-025): the id + full history in the shape analysis\n * tooling consumes. Usage assertions compose it with `summarizeUsageBySource` from\n * `@robota-sdk/agent-session-analytics` in the TEST — the harness itself carries no\n * analytics dependency.\n */\n sessionLog(): { id: string; history: ReturnType<InteractiveSession['getFullHistory']> } {\n return {\n id: this.session.getSession().getSessionId(),\n history: this.session.getFullHistory(),\n };\n }\n\n /** The real session-log directory the framework writes to (`{cwd}/.robota/logs`). */\n logsDir(): string {\n return logsDirOf(this.cwd);\n }\n\n /** Path of the real JSONL transcript the framework writes for this session. */\n transcriptPath(): string {\n return transcriptPathOf(this.cwd, this.session.getSession().getSessionId());\n }\n\n /** Raw contents of the real session transcript (`''` if none was written). */\n transcript(): string {\n return transcriptOf(this.cwd, this.session.getSession().getSessionId());\n }\n\n /**\n * The real session transcript parsed into structured log entries — the durable record the\n * framework itself writes. Leverages the system's own logging as a verification surface.\n */\n logEntries(): Array<Record<string, unknown>> {\n return logEntriesOf(this.cwd, this.session.getSession().getSessionId());\n }\n\n /** Every tool call the agent made across all completed turns, in order. */\n toolCalls(): IToolSummary[] {\n return this.completions.flatMap((result) => result.toolSummaries);\n }\n\n /** Raw collected args of each emission of `event`. */\n emittedEvents<E extends TInteractiveEventName>(\n event: E,\n ): Array<Parameters<IInteractiveSessionEvents[E]>> {\n return (this.events.get(event) ?? []) as Array<Parameters<IInteractiveSessionEvents[E]>>;\n }\n\n /** Read a workspace file (UTF-8). */\n readFile(relPath: string): string {\n return readWorkspaceFile(this.cwd, relPath);\n }\n\n /** Whether a workspace file exists. */\n exists(relPath: string): boolean {\n return workspaceFileExists(this.cwd, relPath);\n }\n\n /** List workspace files (relative paths), excluding the `.robota` session/log dir. */\n files(): string[] {\n return workspaceFiles(this.cwd);\n }\n\n // ── Lifecycle ──────────────────────────────────────────────\n\n /** Shut the session down and remove the temp workspace. Idempotent. */\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n await this.session.shutdown({ reason: 'other', message: 'functional test complete' });\n // Only remove a workspace this harness created — a shared/injected `cwd` (resume/fork) is the\n // caller's to clean up. `maxRetries` rides out the occasional ENOTEMPTY race when a just-written\n // file (e.g. a tool wrote into the workspace) is still settling at teardown.\n if (this.ownsWorkspace) {\n rmSync(this.cwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });\n }\n }\n}\n\n/** Build a live, isolated, scripted functional-test session (TEST-003). */\nexport function scriptedSession(options: IScriptedSessionOptions): ScriptedSessionHarness {\n return new ScriptedSessionHarness(options);\n}\n","/**\n * ARCH-029: the two literals both command-axis doubles read.\n *\n * They live here rather than in either file because the doubles were split by contract axis and\n * these belong to neither: a shared placeholder timestamp, and the fake execution root. Duplicating\n * them would let the two doubles drift on the one thing they must agree about.\n */\n\n/** A timestamp meaning \"this never happened\". */\nexport const NEVER = '1970-01-01T00:00:00.000Z';\n\n/**\n * The root every default cwd hangs off. Deliberately NOT under the shared temp directory: SEC-003's\n * floor treats a hardcoded shared-temp literal as a CWE-377 taint source, and it is right to — the\n * cwd is handed to the production code under test, which may write through it. This path does not\n * exist, so a test that actually writes fails loudly instead of succeeding quietly somewhere world-\n * writable.\n */\nexport const FAKE_ROOT = '/robota-test-command-host';\n","/**\n * ARCH-029: the one guarantee every command-axis double makes about its overrides.\n *\n * It lives here rather than in either double because both make it, and review found the second copy\n * had been re-implemented inline with a comment pointing at \"the sibling double\" — two copies of a\n * guarantee drift, and this one had already been stated wrongly once.\n */\n\n/**\n * An override may replace a member, never remove one.\n *\n * `Partial<T>` admits `{ setPlan: undefined }`, and object spread then writes that `undefined` over\n * the double's answer — reintroducing an ABSENT member at runtime, fully type-checked, which is the\n * exact state ARCH-029 S4 removed.\n *\n * The type alone does NOT close it, and an earlier revision of this comment claimed it did.\n * Measured: `NonNullable` strips `undefined` from the VALUE type, but the `?` modifier re-admits it\n * unless `exactOptionalPropertyTypes` is on, and it is set nowhere in this repo — so\n * `{ setPlan: undefined }` type-checks here with no cast. What the type DOES buy is `null`:\n * `{ setPlan: null }` is rejected under `strictNullChecks`. The `undefined` half of the guarantee is\n * enforced where it actually holds — {@link mergeOverrides} drops those values at merge time, which\n * is flag-independent.\n */\nexport type TOverrides<T> = { [K in keyof T]?: NonNullable<T[K]> };\n\n/**\n * Spread `overrides` over `base`, ignoring any key whose value is `undefined`.\n *\n * This is the enforcement, not the type. `{ ...base, ...overrides }` writes an explicit `undefined`\n * straight through and removes a member the contract requires.\n */\nexport function mergeOverrides<T extends object>(base: T, overrides?: TOverrides<T>): T {\n const merged = { ...base };\n for (const [key, value] of Object.entries(overrides ?? {})) {\n if (value === undefined) continue;\n (merged as Record<string, unknown>)[key] = value;\n }\n return merged;\n}\n","/**\n * ARCH-029: the conformant `IAgentJobHostContext` double.\n *\n * It exists so a test never needs an `as unknown as IAgentJobHostContext` partial. Building the real\n * shape once, here, is what lets every consumer drop that assertion — the double IS the removal of\n * the cast, not a place to hide one.\n *\n * Split out of `command-host-double.ts` when that file passed the anti-monolith limit. The seam is\n * the same one the role ports draw: one file per contract axis.\n */\n\nimport { FAKE_ROOT, NEVER } from './double-constants.js';\nimport { mergeOverrides, type TOverrides } from './double-overrides.js';\n\nimport type { IAgentJobHostContext } from '../command-api/host-context.js';\nimport type {\n IBackgroundJobGroupState,\n IBackgroundTaskState,\n ISubagentJobState,\n} from '@robota-sdk/agent-interface-execution';\n\n/**\n * ARCH-029: the same double, for the capability `ICommandHostContext` reaches through\n * `getAgentJobCapability()`.\n *\n * `IAgentJobHostContext` declares 15 members and **none** of them optional — so it is the more honest\n * of the two contracts, and satisfying it without a cast means answering all fifteen. That is exactly\n * why fixtures cast it: there was nothing to reach for. Migrating a host cast into a job cast would\n * have been half the work, which is why this exists rather than a second double assertion.\n */\n/**\n * The three states this contract returns, each meaning \"nothing ran\". Named rather than inlined so a\n * reader sees they are placeholders, and so the five members returning them cannot drift apart.\n */\n\nconst EMPTY_SUBAGENT_JOB: ISubagentJobState = {\n id: 'test-agent-job',\n type: 'general-purpose',\n label: 'test',\n parentSessionId: 'test-command-host',\n status: 'running',\n mode: 'background',\n depth: 1,\n cwd: FAKE_ROOT,\n promptPreview: '',\n updatedAt: NEVER,\n};\n\nconst EMPTY_JOB_GROUP: IBackgroundJobGroupState = {\n id: 'test-group',\n parentSessionId: 'test-command-host',\n waitPolicy: 'wait_all',\n taskIds: [],\n status: 'running',\n createdAt: NEVER,\n updatedAt: NEVER,\n results: [],\n};\n\nconst EMPTY_BACKGROUND_TASK: IBackgroundTaskState = {\n id: 'test-background-task',\n kind: 'agent',\n label: 'test',\n status: 'running',\n mode: 'background',\n parentSessionId: 'test-command-host',\n depth: 1,\n cwd: FAKE_ROOT,\n updatedAt: NEVER,\n unread: false,\n};\n\nexport function createTestAgentJobHost(\n overrides?: TOverrides<IAgentJobHostContext>,\n): IAgentJobHostContext {\n const base: IAgentJobHostContext = {\n listAgentDefinitions: () => [],\n listAgentJobs: () => [],\n spawnAgentJob: () => Promise.resolve(EMPTY_SUBAGENT_JOB),\n sendAgentJob: () => Promise.resolve(),\n cancelAgentJob: () => Promise.resolve(),\n closeAgentJob: () => Promise.resolve(),\n createBackgroundJobGroup: () => EMPTY_JOB_GROUP,\n waitBackgroundJobGroup: () => Promise.resolve(EMPTY_JOB_GROUP),\n spawnScheduledWake: () => Promise.resolve(EMPTY_BACKGROUND_TASK),\n listSchedules: () => [],\n pauseSchedule: () => Promise.resolve(),\n resumeSchedule: () => Promise.resolve(),\n editSchedule: () => Promise.resolve(),\n spawnMonitorWake: () => Promise.resolve(EMPTY_BACKGROUND_TASK),\n readBackgroundTaskLog: (taskId: string) => Promise.resolve({ taskId, lines: [] }),\n };\n return mergeOverrides(base, overrides);\n}\n","import { createTestAgentJobHost } from './agent-job-host-double.js';\nimport { FAKE_ROOT, NEVER } from './double-constants.js';\nimport { mergeOverrides, type TOverrides } from './double-overrides.js';\n\nimport type { IEditCheckpointRestoreResult } from '../checkpoints/edit-checkpoint-types.js';\nimport type { ICommandHostAdapters } from '../command-api/host-adapters.js';\nimport type { ICommandHostContext, ICommandSessionRuntime } from '../command-api/host-context.js';\nimport type { IMemoryStore } from '../memory/types.js';\nimport type { IGoalState, IPlanArtifact } from '@robota-sdk/agent-interface-session';\n\n/**\n * ARCH-029: a conformant, cast-free `ICommandHostContext` double.\n *\n * ## Why this exists\n *\n * 21 test fixtures reached the command host through a double assertion to it, and another\n * set reaches it through typed literals the cast ratchet cannot see. They are not carelessness —\n * until now there was nothing honest to reach for. A 46-member contract with 32 optional members\n * means a partial object satisfies the compiler while proving nothing about the real host.\n *\n * ARCH-012 solved the identical problem one layer over, and the mechanism that actually killed its\n * 37 casts was **a conformant double placed where every consumer can reach it** — not a runtime\n * capability host. This is that double for the command axis. It lives beside the contract it doubles\n * (`agent-framework` owns `ICommandHostContext`), behind the already-exported `./testing` subpath, so\n * all three consumer packages reach it with no new dependency edge.\n *\n * ## The property that matters\n *\n * The returned object is typed `ICommandHostContext` with **no assertion**. The compiler refuses it\n * the moment the contract gains a member this file does not answer. A double built through a cast —\n * or through a typed factory that lies — would satisfy the cast ratchet and guarantee nothing, which\n * is the failure mode `scan-contract-cast-ratchet.mjs` documents in its own header.\n *\n * ## What the defaults mean\n *\n * Every default answers *\"this host has nothing of that kind\"* — empty lists, `undefined` states,\n * resolved promises. That is deliberate: a test that needs a capability to be PRESENT must say so\n * through `overrides`, so the fixture states its own preconditions instead of inheriting them.\n */\n\n/**\n * A restore that touched nothing. Named rather than inlined twice so the two checkpoint members\n * cannot drift apart, and so a reader sees it is \"nothing happened\", not \"it failed\".\n */\nconst EMPTY_RESTORE_RESULT: IEditCheckpointRestoreResult = {\n target: {\n id: 'test-checkpoint',\n sessionId: 'test-command-host',\n sequence: 0,\n prompt: '',\n createdAt: NEVER,\n fileCount: 0,\n },\n restoredCheckpointCount: 0,\n restoredFileCount: 0,\n removedCheckpointCount: 0,\n};\n\n/** One expression of \"no context used yet\", read by both the host and its session runtime. */\nconst EMPTY_CONTEXT_STATE = {\n maxTokens: 0,\n usedTokens: 0,\n usedPercentage: 0,\n remainingPercentage: 100,\n} as const;\n\n/** \"No goal is in flight\" and \"no plan is in flight\", as values rather than absent members. */\nconst EMPTY_GOAL: IGoalState = {\n id: 'test-goal',\n objective: '',\n status: 'stopped',\n iterations: 0,\n maxIterations: 0,\n startedAt: NEVER,\n progress: [],\n};\n\nconst EMPTY_PLAN: IPlanArtifact = {\n id: 'test-plan',\n objective: '',\n steps: [],\n phase: 'planning',\n createdAt: NEVER,\n};\n\n/** No adapter is injected by default — a test that needs one states it through `overrides`. */\nconst EMPTY_ADAPTERS: ICommandHostAdapters = {};\n\n/** Explicit in-memory absence for command tests; it carries no ambient filesystem authority. */\nconst EMPTY_MEMORY_STORE: IMemoryStore = {\n loadStartupMemory: () =>\n Promise.resolve({ content: '', path: '', lineCount: 0, truncated: false }),\n list: () => Promise.resolve({ indexPath: '', topicsPath: '', topics: [] }),\n readTopic: () => Promise.resolve(''),\n append: (input) =>\n Promise.resolve({ indexPath: '', topicPath: '', topic: input.topic, deduplicated: false }),\n recall: () => Promise.resolve({ content: '', references: [], truncated: false }),\n getPending: () => Promise.resolve(undefined),\n listPending: () => Promise.resolve([]),\n markPending: (id, status, reason) =>\n Promise.resolve({\n id,\n type: 'project',\n topic: '',\n text: '',\n sourceMessageIds: [],\n confidence: 0,\n createdAt: NEVER,\n reason,\n status,\n updatedAt: NEVER,\n }),\n upsertPending: () => Promise.resolve(),\n};\n\n/** Counts doubles so each gets a distinguishable cwd when a test does not name one. */\nlet doublesCreated = 0;\n\n/**\n * The session-runtime half of the double, published for the same reason the host half is: three\n * fixtures hand-rolled this 18-member contract, and making its members required turned each of\n * those into a compile error with nothing honest to reach for.\n */\nexport function createTestSessionRuntime(\n overrides?: TOverrides<ICommandSessionRuntime>,\n): ICommandSessionRuntime {\n // No cast here either. An earlier revision of this file wrote `as ICommandSessionRuntime` over four\n // members of an 18-member contract — the exact defect this double exists to remove, inside the file\n // that removes it. A double built through a helper that casts satisfies the ratchet and guarantees\n // nothing; `scan-contract-cast-ratchet.mjs` says so in its own header.\n const appliedPresetToolLists: {\n allowedTools?: readonly string[];\n deniedTools?: readonly string[];\n }[] = [];\n const base: ICommandSessionRuntime = {\n getSessionId: () => `test-command-host-${doublesCreated}`,\n getHistory: () => [],\n getFullHistory: () => [],\n getMessageCount: () => 0,\n clearHistory: () => {},\n compact: () => Promise.resolve(),\n getContextState: () => EMPTY_CONTEXT_STATE,\n getPermissionMode: () => 'default',\n setPermissionMode: () => {},\n getSessionAllowedTools: () => [],\n getPermissionRules: () => ({ allow: [], deny: [], ask: [] }),\n getRecentPermissionDenials: () => [],\n retryPermissionDenial: () => undefined,\n // ARCH-040 Group C: recorded rather than ignored, so a case can assert the live re-application\n // happened. A double that silently swallows a permission change would let the seam regress green.\n applyPresetToolLists: (preset) => {\n appliedPresetToolLists.push(preset);\n },\n getAutoCompactThreshold: () => false,\n setAutoCompactThreshold: () => {},\n getSessionTokenUsage: () => undefined,\n getModelId: () => undefined,\n getModelEffort: () => 'high',\n // CLI-1990: no tools by default, so a fixture that does not care about the tool surface reports\n // zero schema tokens rather than a made-up figure. A case that does care overrides it.\n getOfferedToolSchemas: () => [],\n addTools: async () => [],\n applyModelOptions: () => {},\n applyAgentName: () => {},\n getActivePresetId: () => 'default',\n setActivePresetId: () => {},\n setParallelSubagentsEnabled: () => {},\n };\n return mergeOverrides(base, overrides);\n}\n\nexport interface ICreateTestCommandHostOptions {\n /** Overrides applied last, so a test can state exactly the capability it exercises. */\n readonly overrides?: TOverrides<ICommandHostContext>;\n /** Convenience for the common case of shaping only the session runtime. */\n readonly session?: TOverrides<ICommandSessionRuntime>;\n /** The working directory every path-scoped command reads. */\n readonly cwd?: string;\n}\n\nexport function createTestCommandHost(\n options: ICreateTestCommandHostOptions = {},\n): ICommandHostContext {\n doublesCreated += 1;\n const cwd = options.cwd ?? `${FAKE_ROOT}-${doublesCreated}`;\n const sessionRuntime = createTestSessionRuntime(options.session);\n\n // No assertion anywhere in this object. The compiler refuses it the moment the contract gains a\n // member this file does not answer — which is the entire property the 21 hand-rolled partials lack.\n const base: ICommandHostContext = {\n getSession: () => sessionRuntime,\n // \"no log was validated\" — an empty, valid report, matching every other default here.\n validateCurrentSessionReplayLog: () => ({\n logFile: `${cwd}/session.jsonl`,\n entryCount: 0,\n validation: { ok: true, issues: [] },\n }),\n getCwd: () => cwd,\n getCommandInvocationSource: () => 'user',\n clearConversationHistory: () => {},\n getSessionUsage: () => [],\n // CLI-1994: \"this host wrote a copy\" — a stable id and the default fork name, nothing on disk.\n forkSession: (input) =>\n Promise.resolve({\n sessionId: `test-command-host-${doublesCreated}-fork`,\n name: input?.name ?? `test-command-host-${doublesCreated} (fork)`,\n }),\n // `undefined` is \"no interactive renderer is attached\" — the headless case, which every\n // command must already handle as a cancellation rather than a silent guess (CMD-004).\n getUserInteraction: () => undefined,\n getActiveOutputStyleId: () => 'default',\n applyPersona: () => {},\n applySelfVerification: () => {},\n applyResponseLanguage: () => {},\n applyOutputStyle: () => {},\n applyPresetSystemPrompt: () => {},\n // An empty array is \"every name matched\" (INFRA-032), not \"nothing was applied\".\n applyCommandModuleSelection: () => [],\n getAutoCompactThresholdSource: () => 'session',\n setAutoCompactThreshold: () => {},\n listCheckpointBranches: () => [],\n forkCheckpointBranch: () => Promise.resolve(EMPTY_RESTORE_RESULT),\n switchCheckpointBranch: () => {},\n // Each goal/plan member answers \"nothing is in flight\".\n setGoal: () => Promise.resolve(EMPTY_GOAL),\n getGoalState: () => null,\n cancelGoal: () => null,\n setPlan: () => Promise.resolve(EMPTY_PLAN),\n getPlanState: () => null,\n approvePlan: () => EMPTY_PLAN,\n revertPlan: () => EMPTY_PLAN,\n getMemoryStore: () => EMPTY_MEMORY_STORE,\n runWithTerminal: (fn) => fn(),\n getContextState: () => EMPTY_CONTEXT_STATE,\n getAutoCompactThreshold: () => false,\n compactContext: () => Promise.resolve(),\n listCommands: () => [],\n listSkills: () => [],\n // `null` is 'this host has no such skill command' — distinct from a command that ran and failed.\n executeSkillCommandByName: () => Promise.resolve(null),\n listContextReferences: () => [],\n // Each answers \"nothing was there\": no reference added, none removed, none evicted.\n addContextReference: () => Promise.resolve({ evicted: [], diagnostics: [] }),\n removeContextReference: () => ({}),\n clearContextReferences: () => ({ removed: [] }),\n listEditCheckpoints: () => [],\n inspectEditCheckpoint: () => ({\n target: EMPTY_RESTORE_RESULT.target,\n capturedFiles: [],\n restoreToCheckpoint: { checkpointIds: [], fileCount: 0 },\n rollbackThroughCheckpoint: { checkpointIds: [], fileCount: 0 },\n }),\n restoreEditCheckpoint: () => Promise.resolve(EMPTY_RESTORE_RESULT),\n rollbackEditCheckpoint: () => Promise.resolve(EMPTY_RESTORE_RESULT),\n getUsedMemoryReferences: () => [],\n recordMemoryEvent: () => {},\n listBackgroundTasks: () => [],\n readBackgroundTaskLog: (taskId: string) => Promise.resolve({ taskId, lines: [] }),\n cancelBackgroundTask: () => Promise.resolve(),\n closeBackgroundTask: () => Promise.resolve(),\n getCommandHostAdapters: () => EMPTY_ADAPTERS,\n canHandoffTerminal: () => false,\n getAgentJobCapability: () => createTestAgentJobHost(),\n };\n\n return mergeOverrides(base, options.overrides);\n}\n","import { createInteractiveRuntimeWithSessionFactory } from '../interaction/createInteractiveRuntime.js';\n\nimport type { IInteractiveRuntime } from '../interaction/InteractiveRuntime.js';\nimport type { IInteractiveRuntimeTestOptions } from '../interaction/createInteractiveRuntime.js';\nimport type { IInteractiveSession } from '../interactive/i-interactive-session.js';\n\n/** Create an interactive runtime around a test double without weakening production options. */\nexport function createInteractiveRuntimeForTesting(\n options: IInteractiveRuntimeTestOptions,\n session: IInteractiveSession,\n): IInteractiveRuntime {\n return createInteractiveRuntimeWithSessionFactory(options, () => session);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robota-sdk/agent-framework",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.82",
|
|
4
4
|
"description": "Programmatic SDK for building AI agents with Robota — provides InteractiveSession, createQuery(), command APIs, permissions, hooks, and context loading",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node/index.js",
|
|
7
7
|
"types": "dist/node/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/node/index.d.ts",
|
|
11
10
|
"source": "./src/index.ts",
|
|
12
11
|
"node": {
|
|
13
|
-
"import":
|
|
14
|
-
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/node/index.d.ts",
|
|
14
|
+
"default": "./dist/node/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/node/index.d.cts",
|
|
18
|
+
"default": "./dist/node/index.cjs"
|
|
19
|
+
}
|
|
15
20
|
},
|
|
16
21
|
"default": {
|
|
17
|
-
"import":
|
|
18
|
-
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/node/index.d.ts",
|
|
24
|
+
"default": "./dist/node/index.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/node/index.d.cts",
|
|
28
|
+
"default": "./dist/node/index.cjs"
|
|
29
|
+
}
|
|
19
30
|
}
|
|
20
31
|
},
|
|
21
32
|
"./testing": {
|
|
22
|
-
"types": "./dist/node/testing/index.d.ts",
|
|
23
33
|
"source": "./src/testing/index.ts",
|
|
24
34
|
"node": {
|
|
25
|
-
"import":
|
|
26
|
-
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/node/testing/index.d.ts",
|
|
37
|
+
"default": "./dist/node/testing/index.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/node/testing/index.d.cts",
|
|
41
|
+
"default": "./dist/node/testing/index.cjs"
|
|
42
|
+
}
|
|
27
43
|
},
|
|
28
44
|
"default": {
|
|
29
|
-
"import":
|
|
30
|
-
|
|
45
|
+
"import": {
|
|
46
|
+
"types": "./dist/node/testing/index.d.ts",
|
|
47
|
+
"default": "./dist/node/testing/index.js"
|
|
48
|
+
},
|
|
49
|
+
"require": {
|
|
50
|
+
"types": "./dist/node/testing/index.d.cts",
|
|
51
|
+
"default": "./dist/node/testing/index.cjs"
|
|
52
|
+
}
|
|
31
53
|
}
|
|
32
54
|
}
|
|
33
55
|
},
|
|
@@ -43,19 +65,32 @@
|
|
|
43
65
|
"dist"
|
|
44
66
|
],
|
|
45
67
|
"dependencies": {
|
|
68
|
+
"yaml": "^2.9.0",
|
|
46
69
|
"zod": "^3.25.76",
|
|
47
|
-
"@robota-sdk/agent-
|
|
48
|
-
"@robota-sdk/agent-interface-
|
|
49
|
-
"@robota-sdk/agent-
|
|
50
|
-
"@robota-sdk/agent-
|
|
51
|
-
"@robota-sdk/agent-
|
|
70
|
+
"@robota-sdk/agent-executor": "3.0.0-beta.82",
|
|
71
|
+
"@robota-sdk/agent-interface-analytics": "3.0.0-beta.82",
|
|
72
|
+
"@robota-sdk/agent-file-authority": "3.0.0-beta.82",
|
|
73
|
+
"@robota-sdk/agent-core": "3.0.0-beta.82",
|
|
74
|
+
"@robota-sdk/agent-interface-command": "3.0.0-beta.82",
|
|
75
|
+
"@robota-sdk/agent-interface-execution": "3.0.0-beta.82",
|
|
76
|
+
"@robota-sdk/agent-interface-session": "3.0.0-beta.82",
|
|
77
|
+
"@robota-sdk/agent-interface-session-mobility": "3.0.0-beta.82",
|
|
78
|
+
"@robota-sdk/agent-interface-transport": "3.0.0-beta.82",
|
|
79
|
+
"@robota-sdk/agent-session": "3.0.0-beta.82",
|
|
80
|
+
"@robota-sdk/agent-tool-defaults": "3.0.0-beta.82",
|
|
81
|
+
"@robota-sdk/agent-tools": "3.0.0-beta.82"
|
|
52
82
|
},
|
|
53
83
|
"devDependencies": {
|
|
84
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
85
|
"rimraf": "^5.0.10",
|
|
55
|
-
"tsdown": "^0.22.
|
|
56
|
-
"
|
|
86
|
+
"tsdown": "^0.22.14",
|
|
87
|
+
"tsx": "^4.23.1",
|
|
88
|
+
"typescript": "^6.0.3",
|
|
57
89
|
"vitest": "^3.2.6",
|
|
58
|
-
"@robota-sdk/agent-
|
|
90
|
+
"@robota-sdk/agent-provider-openai-compatible": "3.0.0-beta.82",
|
|
91
|
+
"@robota-sdk/agent-provider-replay": "3.0.0-beta.82",
|
|
92
|
+
"@robota-sdk/agent-session-analytics": "3.0.0-beta.82",
|
|
93
|
+
"@robota-sdk/agent-transport-mcp": "3.0.0-beta.82"
|
|
59
94
|
},
|
|
60
95
|
"keywords": [
|
|
61
96
|
"ai",
|
|
@@ -78,14 +113,25 @@
|
|
|
78
113
|
"publishConfig": {
|
|
79
114
|
"access": "public"
|
|
80
115
|
},
|
|
116
|
+
"volta": {
|
|
117
|
+
"extends": "../../package.json"
|
|
118
|
+
},
|
|
81
119
|
"scripts": {
|
|
82
|
-
"build": "tsdown",
|
|
83
|
-
"build:js": "
|
|
84
|
-
"build:types": "
|
|
120
|
+
"build": "rimraf dist && tsdown",
|
|
121
|
+
"build:js": "pnpm run build",
|
|
122
|
+
"build:types": "pnpm run build",
|
|
85
123
|
"test": "vitest run --passWithNoTests",
|
|
86
124
|
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
87
|
-
"
|
|
125
|
+
"scenario:verify": "pnpm scenario:verify:external-payload && pnpm scenario:verify:runtime-session-store && pnpm scenario:verify:prompt-resolution && pnpm scenario:verify:zero-config-tools && pnpm scenario:verify:workspace-authority && pnpm scenario:verify:memory-recall-provenance && pnpm scenario:verify:fork-record-persistence",
|
|
126
|
+
"scenario:verify:external-payload": "pnpm exec tsx --conditions=source examples/verify-session-log-external-payload-replay.ts",
|
|
127
|
+
"scenario:verify:runtime-session-store": "pnpm exec tsx --conditions=source examples/verify-agent-runtime-session-store.ts",
|
|
128
|
+
"scenario:verify:prompt-resolution": "pnpm exec tsx --conditions=source examples/verify-prompt-request-resolution.ts",
|
|
129
|
+
"typecheck": "tsgo --noEmit && tsgo -p tsconfig.examples.json --noEmit",
|
|
88
130
|
"lint": "eslint src/ --ext .ts",
|
|
89
|
-
"clean": "rimraf dist"
|
|
131
|
+
"clean": "rimraf dist",
|
|
132
|
+
"scenario:verify:zero-config-tools": "pnpm exec tsx --conditions=source examples/verify-zero-config-default-tools.ts",
|
|
133
|
+
"scenario:verify:workspace-authority": "pnpm exec tsx --conditions=source examples/verify-workspace-project-authority.ts",
|
|
134
|
+
"scenario:verify:memory-recall-provenance": "pnpm exec tsx --conditions=source examples/verify-memory-recall-provenance.ts",
|
|
135
|
+
"scenario:verify:fork-record-persistence": "pnpm exec tsx examples/verify-fork-record-persistence.ts"
|
|
90
136
|
}
|
|
91
137
|
}
|