diff --git a/extensions/qa-lab/src/suite.ts b/extensions/qa-lab/src/suite.ts index c662aa0c0cf..871b4db0e3f 100644 --- a/extensions/qa-lab/src/suite.ts +++ b/extensions/qa-lab/src/suite.ts @@ -86,11 +86,13 @@ export type QaSuiteRunParams = { concurrency?: number; }; -async function startQaLabServerRuntime( - params?: QaLabServerStartParams, -): Promise { - const { startQaLabServer } = await import("./lab-server.js"); - return await startQaLabServer(params); +function requireQaSuiteStartLab(startLab: QaSuiteStartLabFn | undefined): QaSuiteStartLabFn { + if (startLab) { + return startLab; + } + throw new Error( + "QA suite requires startLab when no lab handle is provided; use the runtime launcher or pass startLab explicitly.", + ); } const _QA_IMAGE_UNDERSTANDING_PNG_BASE64 = @@ -1402,7 +1404,7 @@ export async function runQaSuite(params?: QaSuiteRunParams): Promise 1 && selectedCatalogScenarios.length > 1) { const ownsLab = !params?.lab; - const startLab = params?.startLab ?? startQaLabServerRuntime; + const startLab = requireQaSuiteStartLab(params?.startLab); const lab = params?.lab ?? (await startLab({ @@ -1452,6 +1454,7 @@ export async function runQaSuite(params?: QaSuiteRunParams): Promise; -}; +export type { CompactEmbeddedPiSessionParams } from "./compact.types.js"; function hasRealConversationContent( msg: AgentMessage, diff --git a/src/agents/pi-embedded-runner/compact.types.ts b/src/agents/pi-embedded-runner/compact.types.ts new file mode 100644 index 00000000000..d82671301db --- /dev/null +++ b/src/agents/pi-embedded-runner/compact.types.ts @@ -0,0 +1,67 @@ +import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js"; +import type { OpenClawConfig } from "../../config/config.js"; +import type { enqueueCommand } from "../../process/command-queue.js"; +import type { ExecElevatedDefaults } from "../bash-tools.js"; +import type { SkillSnapshot } from "../skills.js"; + +export type CompactEmbeddedPiSessionParams = { + sessionId: string; + runId?: string; + sessionKey?: string; + messageChannel?: string; + messageProvider?: string; + agentAccountId?: string; + currentChannelId?: string; + currentThreadTs?: string; + currentMessageId?: string | number; + /** Trusted sender id from inbound context for scoped message-tool discovery. */ + senderId?: string; + senderName?: string; + senderUsername?: string; + senderE164?: string; + authProfileId?: string; + /** Group id for channel-level tool policy resolution. */ + groupId?: string | null; + /** Group channel label (e.g. #general) for channel-level tool policy resolution. */ + groupChannel?: string | null; + /** Group space label (e.g. guild/team id) for channel-level tool policy resolution. */ + groupSpace?: string | null; + /** Parent session key for subagent policy inheritance. */ + spawnedBy?: string | null; + /** Whether the sender is an owner (required for owner-only tools). */ + senderIsOwner?: boolean; + sessionFile: string; + /** Optional caller-observed live prompt tokens used for compaction diagnostics. */ + currentTokenCount?: number; + workspaceDir: string; + agentDir?: string; + config?: OpenClawConfig; + skillsSnapshot?: SkillSnapshot; + provider?: string; + model?: string; + thinkLevel?: ThinkLevel; + reasoningLevel?: ReasoningLevel; + bashElevated?: ExecElevatedDefaults; + customInstructions?: string; + tokenBudget?: number; + force?: boolean; + trigger?: "budget" | "overflow" | "manual"; + diagId?: string; + attempt?: number; + maxAttempts?: number; + lane?: string; + enqueue?: typeof enqueueCommand; + extraSystemPrompt?: string; + ownerNumbers?: string[]; + abortSignal?: AbortSignal; + /** Allow runtime plugins for this compaction to late-bind the gateway subagent. */ + allowGatewaySubagentBinding?: boolean; +}; + +export type CompactionMessageMetrics = { + messages: number; + historyTextChars: number; + toolResultChars: number; + estTokens?: number; + contributors: Array<{ role: string; chars: number; tool?: string }>; +}; diff --git a/src/agents/pi-embedded-runner/run/types.ts b/src/agents/pi-embedded-runner/run/types.ts index a377aa5f493..52e3e276bfb 100644 --- a/src/agents/pi-embedded-runner/run/types.ts +++ b/src/agents/pi-embedded-runner/run/types.ts @@ -4,7 +4,7 @@ import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; import type { ThinkLevel } from "../../../auto-reply/thinking.js"; import type { SessionSystemPromptReport } from "../../../config/sessions/types.js"; import type { ContextEngine, ContextEnginePromptCacheInfo } from "../../../context-engine/types.js"; -import type { PluginHookBeforeAgentStartResult } from "../../../plugins/types.js"; +import type { PluginHookBeforeAgentStartResult } from "../../../plugins/hook-before-agent-start.types.js"; import type { MessagingToolSend } from "../../pi-embedded-messaging.js"; import type { ToolErrorSummary } from "../../tool-error-summary.js"; import type { NormalizedUsage } from "../../usage.js"; diff --git a/src/agents/sandbox/backend.types.ts b/src/agents/sandbox/backend.types.ts new file mode 100644 index 00000000000..32490b9f1bc --- /dev/null +++ b/src/agents/sandbox/backend.types.ts @@ -0,0 +1,49 @@ +import type { OpenClawConfig } from "../../config/config.js"; +import type { SandboxBackendHandle, SandboxBackendId } from "./backend-handle.types.js"; +import type { SandboxRegistryEntry } from "./registry.js"; +import type { SandboxConfig } from "./types.js"; + +export type SandboxBackendRuntimeInfo = { + running: boolean; + actualConfigLabel?: string; + configLabelMatch: boolean; +}; + +export type SandboxBackendManager = { + describeRuntime(params: { + entry: SandboxRegistryEntry; + config: OpenClawConfig; + agentId?: string; + }): Promise; + removeRuntime(params: { + entry: SandboxRegistryEntry; + config: OpenClawConfig; + agentId?: string; + }): Promise; +}; + +export type CreateSandboxBackendParams = { + sessionKey: string; + scopeKey: string; + workspaceDir: string; + agentWorkspaceDir: string; + cfg: SandboxConfig; +}; + +export type SandboxBackendFactory = ( + params: CreateSandboxBackendParams, +) => Promise; + +export type SandboxBackendRegistration = + | SandboxBackendFactory + | { + factory: SandboxBackendFactory; + manager?: SandboxBackendManager; + }; + +export type RegisteredSandboxBackend = { + factory: SandboxBackendFactory; + manager?: SandboxBackendManager; +}; + +export type { SandboxBackendHandle, SandboxBackendId } from "./backend-handle.types.js"; diff --git a/src/agents/sandbox/ssh-backend.ts b/src/agents/sandbox/ssh-backend.ts index e4a51e46818..6902af151d8 100644 --- a/src/agents/sandbox/ssh-backend.ts +++ b/src/agents/sandbox/ssh-backend.ts @@ -6,7 +6,7 @@ import type { SandboxBackendCommandResult, SandboxBackendHandle, SandboxBackendManager, -} from "./backend.js"; +} from "./backend.types.js"; import { resolveSandboxConfigForAgent } from "./config.js"; import { createRemoteShellSandboxFsBridge, diff --git a/src/agents/subagent-announce-delivery.ts b/src/agents/subagent-announce-delivery.ts index 6027e48de0f..56cc7cab5dc 100644 --- a/src/agents/subagent-announce-delivery.ts +++ b/src/agents/subagent-announce-delivery.ts @@ -38,7 +38,7 @@ import { resolveAnnounceOrigin, type DeliveryContext } from "./subagent-announce import { type AnnounceQueueItem, enqueueAnnounce } from "./subagent-announce-queue.js"; import { getSubagentDepthFromSessionStore } from "./subagent-depth.js"; import { resolveRequesterStoreKey } from "./subagent-requester-store-key.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.js"; +import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; export { resolveAnnounceOrigin } from "./subagent-announce-origin.js"; diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index fe068897b73..422ca59f5e6 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -36,7 +36,7 @@ import { waitForEmbeddedPiRunEnd, } from "./subagent-announce.runtime.js"; import { getSubagentDepthFromSessionStore } from "./subagent-depth.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.js"; +import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; import { isAnnounceSkip } from "./tools/sessions-send-tokens.js"; type SubagentAnnounceDeps = { diff --git a/src/agents/subagent-registry.types.ts b/src/agents/subagent-registry.types.ts index 29c4cec30bf..113e2c799cd 100644 --- a/src/agents/subagent-registry.types.ts +++ b/src/agents/subagent-registry.types.ts @@ -1,7 +1,7 @@ import type { DeliveryContext } from "../utils/delivery-context.js"; import type { SubagentRunOutcome } from "./subagent-announce.js"; import type { SubagentLifecycleEndedReason } from "./subagent-lifecycle-events.js"; -import type { SpawnSubagentMode } from "./subagent-spawn.js"; +import type { SpawnSubagentMode } from "./subagent-spawn.types.js"; export type SubagentRunRecord = { runId: string; diff --git a/src/agents/subagent-spawn.ts b/src/agents/subagent-spawn.ts index 2326922d78b..f7ca83329bd 100644 --- a/src/agents/subagent-spawn.ts +++ b/src/agents/subagent-spawn.ts @@ -51,11 +51,15 @@ import { updateSessionStore, isAdminOnlyMethod, } from "./subagent-spawn.runtime.js"; +import { + SUBAGENT_SPAWN_MODES, + SUBAGENT_SPAWN_SANDBOX_MODES, + type SpawnSubagentMode, + type SpawnSubagentSandboxMode, +} from "./subagent-spawn.types.js"; -export const SUBAGENT_SPAWN_MODES = ["run", "session"] as const; -export type SpawnSubagentMode = (typeof SUBAGENT_SPAWN_MODES)[number]; -export const SUBAGENT_SPAWN_SANDBOX_MODES = ["inherit", "require"] as const; -export type SpawnSubagentSandboxMode = (typeof SUBAGENT_SPAWN_SANDBOX_MODES)[number]; +export { SUBAGENT_SPAWN_MODES, SUBAGENT_SPAWN_SANDBOX_MODES } from "./subagent-spawn.types.js"; +export type { SpawnSubagentMode, SpawnSubagentSandboxMode } from "./subagent-spawn.types.js"; export { decodeStrictBase64 }; diff --git a/src/agents/subagent-spawn.types.ts b/src/agents/subagent-spawn.types.ts new file mode 100644 index 00000000000..bf1bd7f6586 --- /dev/null +++ b/src/agents/subagent-spawn.types.ts @@ -0,0 +1,5 @@ +export const SUBAGENT_SPAWN_MODES = ["run", "session"] as const; +export type SpawnSubagentMode = (typeof SUBAGENT_SPAWN_MODES)[number]; + +export const SUBAGENT_SPAWN_SANDBOX_MODES = ["inherit", "require"] as const; +export type SpawnSubagentSandboxMode = (typeof SUBAGENT_SPAWN_SANDBOX_MODES)[number]; diff --git a/src/plugins/hook-before-agent-start.types.ts b/src/plugins/hook-before-agent-start.types.ts new file mode 100644 index 00000000000..6236cf05d53 --- /dev/null +++ b/src/plugins/hook-before-agent-start.types.ts @@ -0,0 +1,80 @@ +// before_model_resolve hook +export type PluginHookBeforeModelResolveEvent = { + /** User prompt for this run. No session messages are available yet in this phase. */ + prompt: string; +}; + +export type PluginHookBeforeModelResolveResult = { + /** Override the model for this agent run. E.g. "llama3.3:8b" */ + modelOverride?: string; + /** Override the provider for this agent run. E.g. "ollama" */ + providerOverride?: string; +}; + +// before_prompt_build hook +export type PluginHookBeforePromptBuildEvent = { + prompt: string; + /** Session messages prepared for this run. */ + messages: unknown[]; +}; + +export type PluginHookBeforePromptBuildResult = { + systemPrompt?: string; + prependContext?: string; + /** + * Prepended to the agent system prompt so providers can cache it (e.g. prompt caching). + * Use for static plugin guidance instead of prependContext to avoid per-turn token cost. + */ + prependSystemContext?: string; + /** + * Appended to the agent system prompt so providers can cache it (e.g. prompt caching). + * Use for static plugin guidance instead of prependContext to avoid per-turn token cost. + */ + appendSystemContext?: string; +}; + +export const PLUGIN_PROMPT_MUTATION_RESULT_FIELDS = [ + "systemPrompt", + "prependContext", + "prependSystemContext", + "appendSystemContext", +] as const satisfies readonly (keyof PluginHookBeforePromptBuildResult)[]; + +type MissingPluginPromptMutationResultFields = Exclude< + keyof PluginHookBeforePromptBuildResult, + (typeof PLUGIN_PROMPT_MUTATION_RESULT_FIELDS)[number] +>; +type AssertAllPluginPromptMutationResultFieldsListed = + MissingPluginPromptMutationResultFields extends never ? true : never; +const assertAllPluginPromptMutationResultFieldsListed: AssertAllPluginPromptMutationResultFieldsListed = true; +void assertAllPluginPromptMutationResultFieldsListed; + +// before_agent_start hook (legacy compatibility: combines both phases) +export type PluginHookBeforeAgentStartEvent = { + prompt: string; + /** Optional because legacy hook can run in pre-session phase. */ + messages?: unknown[]; +}; + +export type PluginHookBeforeAgentStartResult = PluginHookBeforePromptBuildResult & + PluginHookBeforeModelResolveResult; + +export type PluginHookBeforeAgentStartOverrideResult = Omit< + PluginHookBeforeAgentStartResult, + keyof PluginHookBeforePromptBuildResult +>; + +export const stripPromptMutationFieldsFromLegacyHookResult = ( + result: PluginHookBeforeAgentStartResult | void, +): PluginHookBeforeAgentStartOverrideResult | void => { + if (!result || typeof result !== "object") { + return result; + } + const remaining: Partial = { ...result }; + for (const field of PLUGIN_PROMPT_MUTATION_RESULT_FIELDS) { + delete remaining[field]; + } + return Object.keys(remaining).length > 0 + ? (remaining as PluginHookBeforeAgentStartOverrideResult) + : undefined; +}; diff --git a/src/security/audit-channel.ts b/src/security/audit-channel.ts index bcdad196cd8..d5a3487a50e 100644 --- a/src/security/audit-channel.ts +++ b/src/security/audit-channel.ts @@ -10,7 +10,7 @@ import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig } from "../config/config.js"; import { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matching.js"; import { formatErrorMessage } from "../infra/errors.js"; -import type { SecurityAuditFinding, SecurityAuditSeverity } from "./audit.js"; +import type { SecurityAuditFinding, SecurityAuditSeverity } from "./audit.types.js"; import { resolveDmAllowState } from "./dm-policy-shared.js"; function classifyChannelWarningSeverity(message: string): SecurityAuditSeverity { diff --git a/src/security/audit.ts b/src/security/audit.ts index b3321771c28..6ecee83ff49 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -31,6 +31,12 @@ import { formatPermissionRemediation, inspectPathPermissions, } from "./audit-fs.js"; +import type { + SecurityAuditFinding, + SecurityAuditReport, + SecurityAuditSeverity, + SecurityAuditSummary, +} from "./audit.types.js"; import { collectEnabledInsecureOrDangerousFlags } from "./dangerous-config-flags.js"; import { DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "./dangerous-tools.js"; import type { ExecFn } from "./windows-acl.js"; @@ -38,36 +44,12 @@ import type { ExecFn } from "./windows-acl.js"; type ExecDockerRawFn = typeof import("../agents/sandbox/docker.js").execDockerRaw; type ProbeGatewayFn = typeof import("../gateway/probe.js").probeGateway; -export type SecurityAuditSeverity = "info" | "warn" | "critical"; - -export type SecurityAuditFinding = { - checkId: string; - severity: SecurityAuditSeverity; - title: string; - detail: string; - remediation?: string; -}; - -export type SecurityAuditSummary = { - critical: number; - warn: number; - info: number; -}; - -export type SecurityAuditReport = { - ts: number; - summary: SecurityAuditSummary; - findings: SecurityAuditFinding[]; - deep?: { - gateway?: { - attempted: boolean; - url: string | null; - ok: boolean; - error: string | null; - close?: { code: number; reason: string } | null; - }; - }; -}; +export type { + SecurityAuditFinding, + SecurityAuditReport, + SecurityAuditSeverity, + SecurityAuditSummary, +} from "./audit.types.js"; export type SecurityAuditOptions = { config: OpenClawConfig; diff --git a/src/security/audit.types.ts b/src/security/audit.types.ts new file mode 100644 index 00000000000..3e22a77527a --- /dev/null +++ b/src/security/audit.types.ts @@ -0,0 +1,30 @@ +export type SecurityAuditSeverity = "info" | "warn" | "critical"; + +export type SecurityAuditFinding = { + checkId: string; + severity: SecurityAuditSeverity; + title: string; + detail: string; + remediation?: string; +}; + +export type SecurityAuditSummary = { + critical: number; + warn: number; + info: number; +}; + +export type SecurityAuditReport = { + ts: number; + summary: SecurityAuditSummary; + findings: SecurityAuditFinding[]; + deep?: { + gateway?: { + attempted: boolean; + url: string | null; + ok: boolean; + error: string | null; + close?: { code: number; reason: string } | null; + }; + }; +};