fix(cycles): continue shared seam extraction

This commit is contained in:
Vincent Koc
2026-04-11 01:35:19 +01:00
parent 81235fd923
commit 350299401f
17 changed files with 274 additions and 116 deletions

View File

@@ -86,11 +86,13 @@ export type QaSuiteRunParams = {
concurrency?: number;
};
async function startQaLabServerRuntime(
params?: QaLabServerStartParams,
): Promise<QaLabServerHandle> {
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<QaSuiteResu
if (concurrency > 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<QaSuiteResu
claudeCliAuthMode: params?.claudeCliAuthMode,
scenarioIds: [scenario.id],
concurrency: 1,
startLab,
});
const scenarioResult: QaSuiteScenarioResult =
result.scenarios[0] ??
@@ -1547,10 +1550,10 @@ export async function runQaSuite(params?: QaSuiteRunParams): Promise<QaSuiteResu
}
const ownsLab = !params?.lab;
const startLab = params?.startLab ?? startQaLabServerRuntime;
const startLab = params?.startLab;
const lab =
params?.lab ??
(await startLab({
(await requireQaSuiteStartLab(startLab)({
repoRoot,
host: "127.0.0.1",
port: 0,

View File

@@ -3,7 +3,7 @@ import type { AgentEmbeddedHarnessConfig } from "../../config/types.agents-share
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import { listAgentEntries, resolveSessionAgentIds } from "../agent-scope.js";
import type { CompactEmbeddedPiSessionParams } from "../pi-embedded-runner/compact.js";
import type { CompactEmbeddedPiSessionParams } from "../pi-embedded-runner/compact.types.js";
import type {
EmbeddedRunAttemptParams,
EmbeddedRunAttemptResult,

View File

@@ -1,4 +1,4 @@
import type { CompactEmbeddedPiSessionParams } from "../pi-embedded-runner/compact.js";
import type { CompactEmbeddedPiSessionParams } from "../pi-embedded-runner/compact.types.js";
import type {
EmbeddedRunAttemptParams,
EmbeddedRunAttemptResult,

View File

@@ -7,7 +7,7 @@ import {
estimateTokens,
SessionManager,
} from "@mariozechner/pi-coding-agent";
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
import type { ThinkLevel } from "../../auto-reply/thinking.js";
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
@@ -33,7 +33,7 @@ import {
transformProviderSystemPrompt,
} from "../../plugins/provider-runtime.js";
import type { ProviderRuntimeModel } from "../../plugins/types.js";
import { type enqueueCommand, enqueueCommandInLane } from "../../process/command-queue.js";
import { enqueueCommandInLane } from "../../process/command-queue.js";
import { isCronSessionKey, isSubagentSessionKey } from "../../routing/session-key.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import { buildTtsSystemPromptHint } from "../../tts/tts.js";
@@ -42,7 +42,6 @@ import { normalizeMessageChannel } from "../../utils/message-channel.js";
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
import { resolveOpenClawAgentDir } from "../agent-paths.js";
import { resolveSessionAgentIds } from "../agent-scope.js";
import type { ExecElevatedDefaults } from "../bash-tools.js";
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../bootstrap-files.js";
import {
listChannelSupportedActions,
@@ -95,11 +94,11 @@ import {
applySkillEnvOverrides,
applySkillEnvOverridesFromSnapshot,
resolveSkillsPromptForRun,
type SkillSnapshot,
} from "../skills.js";
import { resolveSystemPromptOverride } from "../system-prompt-override.js";
import { resolveTranscriptPolicy } from "../transcript-policy.js";
import { classifyCompactionReason, resolveCompactionFailureReason } from "./compact-reasons.js";
import type { CompactEmbeddedPiSessionParams, CompactionMessageMetrics } from "./compact.types.js";
import {
asCompactionHookRunner,
buildBeforeCompactionHookMetrics,
@@ -151,68 +150,7 @@ import { splitSdkTools } from "./tool-split.js";
import type { EmbeddedPiCompactResult } from "./types.js";
import { mapThinkingLevel } from "./utils.js";
import { flushPendingToolResultsAfterIdle } from "./wait-for-idle-before-flush.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;
};
type CompactionMessageMetrics = {
messages: number;
historyTextChars: number;
toolResultChars: number;
estTokens?: number;
contributors: Array<{ role: string; chars: number; tool?: string }>;
};
export type { CompactEmbeddedPiSessionParams } from "./compact.types.js";
function hasRealConversationContent(
msg: AgentMessage,

View File

@@ -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 }>;
};

View File

@@ -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";

View File

@@ -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<SandboxBackendRuntimeInfo>;
removeRuntime(params: {
entry: SandboxRegistryEntry;
config: OpenClawConfig;
agentId?: string;
}): Promise<void>;
};
export type CreateSandboxBackendParams = {
sessionKey: string;
scopeKey: string;
workspaceDir: string;
agentWorkspaceDir: string;
cfg: SandboxConfig;
};
export type SandboxBackendFactory = (
params: CreateSandboxBackendParams,
) => Promise<SandboxBackendHandle>;
export type SandboxBackendRegistration =
| SandboxBackendFactory
| {
factory: SandboxBackendFactory;
manager?: SandboxBackendManager;
};
export type RegisteredSandboxBackend = {
factory: SandboxBackendFactory;
manager?: SandboxBackendManager;
};
export type { SandboxBackendHandle, SandboxBackendId } from "./backend-handle.types.js";

View File

@@ -6,7 +6,7 @@ import type {
SandboxBackendCommandResult,
SandboxBackendHandle,
SandboxBackendManager,
} from "./backend.js";
} from "./backend.types.js";
import { resolveSandboxConfigForAgent } from "./config.js";
import {
createRemoteShellSandboxFsBridge,

View File

@@ -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";

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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];

View File

@@ -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<PluginHookBeforeAgentStartResult> = { ...result };
for (const field of PLUGIN_PROMPT_MUTATION_RESULT_FIELDS) {
delete remaining[field];
}
return Object.keys(remaining).length > 0
? (remaining as PluginHookBeforeAgentStartOverrideResult)
: undefined;
};

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;
};
};
};