mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-20 17:41:33 -06:00
1ca60fbc3a
* refactor(agents): make roster ownership explicit * feat(config): materialize legacy agent roles * fix(cron): migrate legacy owners at startup * feat(gateway): expose agent selection contracts * fix(gateway): enforce agent-scoped authorization * docs(config): document explicit agent ownership * fix(config): pin retained owner workspace * fix(gateway): target hook wakes at effective agent * fix(sessions): preserve fixed-store ownership * fix: preserve retained agent ownership * fix: preserve legacy agent ownership across runtime surfaces * fix: fail closed on ambiguous session ownership * fix: preserve compatibility owners across dispatch and writes * fix: preserve retained agent projections * fix: preserve agent ownership compatibility * fix: preserve per-agent heartbeat guidance * fix: preserve compatibility owners in generic paths * fix: enforce configured ownership in session paths * fix: defer remote roster selection * fix: preserve ownership across session and config writes * fix: fail closed on ambiguous restored ownership * fix: preserve explicit ACP and legacy ownership * fix: honor durable fixed-store ownership * fix: enforce fixed-store owner authority * fix: preserve ownership evidence boundaries * fix: honor resolved session ownership * fix: align compatibility ownership paths * fix: persist legacy main store ownership * fix: close ownership fallback gaps * fix(agents): close retained owner compatibility gaps * fix(agents): enforce session owner resolution * fix(agents): complete session owner resolution sweep * fix(agents): preserve durable session ownership * fix: complete persisted session owner routing * fix: thread prepared session owners * fix: preserve stable session ownership * fix: enforce session ownership boundaries * fix: close session ownership delta gaps * fix: reconcile session ownership after rebase * fix: reconcile ownership with current main * fix: align session store path imports * fix: align session store config path import * fix: reconcile explicit ownership CI * fix: reconcile ownership rebase checks * fix: align ownership ci contracts * fix: align ownership rebase checks * fix: preserve compatibility owner during setup * fix(doctor): migrate ownerless heartbeat monitors * fix(gateway): preserve explicit session ownership * test: align ownership fixtures after rebase * test: complete plugin manifest fixture * test: align runtime context mocks * fix(gateway): preserve alias routing for existing sessions * style: format agent routing update * fix(gateway): preserve selected owner during alias routing * style: normalize rebased ownership files * fix(gateway): preserve owner through global alias routing * fix(gateway): preserve explicit ownership at HTTP boundaries * fix(gateway): validate compatibility model ownership * fix(agents): reconcile strict session ownership * fix(agents): contain media yield callback failures * fix(agents): avoid eager bare-key owner resolution * chore: refresh rebased ownership baselines * chore: align hosted plugin SDK baseline * chore: refresh ownership baselines after main sync * chore: refresh ownership baselines after main sync * test: align routed event owner fixtures * chore: retrigger CI after runner startup failure * chore: refresh ownership SDK budgets after main sync * fix(tasks): require agent identity for bare owners * chore: align Linux plugin SDK baseline * chore: remove release-owned changelog entry
339 lines
9.5 KiB
TypeScript
339 lines
9.5 KiB
TypeScript
/** Public host-hook type contracts exposed to plugin runtimes. */
|
|
import type { OperatorScope } from "../gateway/operator-scopes.js";
|
|
import type { AgentEventPayload, AgentEventStream } from "../infra/agent-events.js";
|
|
import type {
|
|
PluginHookBeforeToolCallEvent,
|
|
PluginHookBeforeToolCallResult,
|
|
PluginHookToolContext,
|
|
PluginToolMatcher,
|
|
} from "./hook-types.js";
|
|
import type { PluginJsonValue } from "./host-hook-json.js";
|
|
import type {
|
|
PluginAgentTurnPrepareResult,
|
|
PluginNextTurnInjectionPlacement,
|
|
PluginNextTurnInjectionRecord,
|
|
} from "./host-hook-turn-types.js";
|
|
|
|
export { isPluginJsonValue } from "./host-hook-json.js";
|
|
export type { PluginJsonValue } from "./host-hook-json.js";
|
|
export type {
|
|
PluginAgentTurnPrepareEvent,
|
|
PluginAgentTurnPrepareResult,
|
|
PluginHeartbeatPromptContributionEvent,
|
|
PluginHeartbeatPromptContributionResult,
|
|
PluginNextTurnInjection,
|
|
PluginNextTurnInjectionEnqueueResult,
|
|
PluginNextTurnInjectionRecord,
|
|
} from "./host-hook-turn-types.js";
|
|
|
|
/** Reason passed to plugin cleanup callbacks when host-owned state changes. */
|
|
export type PluginHostCleanupReason = "disable" | "reset" | "delete" | "restart";
|
|
|
|
type PluginSessionExtensionProjectionContext = {
|
|
sessionKey: string;
|
|
sessionId?: string;
|
|
state: PluginJsonValue | undefined;
|
|
};
|
|
|
|
/** Session extension registration owned by a plugin namespace. */
|
|
export type PluginSessionExtensionRegistration = {
|
|
namespace: string;
|
|
description: string;
|
|
project?: (ctx: PluginSessionExtensionProjectionContext) => PluginJsonValue | undefined;
|
|
cleanup?: (ctx: { reason: PluginHostCleanupReason; sessionKey?: string }) => void | Promise<void>;
|
|
/**
|
|
* When set, after every successful `patchSessionExtension` the projected
|
|
* value is mirrored to `SessionEntry[<slotKey>]` so non-plugin readers
|
|
* can consume the typed slot without reaching into
|
|
* `pluginExtensions[pluginId][namespace]`.
|
|
*
|
|
* The slot is a read-only mirror: writes always go through
|
|
* `patchSessionExtension`; the host overwrites the slot value on every
|
|
* subsequent patch.
|
|
*/
|
|
sessionEntrySlotKey?: string;
|
|
/**
|
|
* Optional JSON-compatible schema describing the projected slot value.
|
|
* Purely informational at this layer; clients may use it to validate the
|
|
* mirrored slot against a contract.
|
|
*/
|
|
sessionEntrySlotSchema?: PluginJsonValue;
|
|
};
|
|
|
|
export type PluginSessionExtensionProjection = {
|
|
pluginId: string;
|
|
namespace: string;
|
|
value: PluginJsonValue;
|
|
};
|
|
|
|
type PluginToolPolicyDecision =
|
|
| PluginHookBeforeToolCallResult
|
|
| {
|
|
allow?: boolean;
|
|
reason?: string;
|
|
};
|
|
|
|
export type PluginTrustedToolPolicyRegistration = {
|
|
id: string;
|
|
description: string;
|
|
matcher?: PluginToolMatcher;
|
|
evaluate: (
|
|
event: PluginHookBeforeToolCallEvent,
|
|
ctx: PluginHookToolContext,
|
|
) => PluginToolPolicyDecision | void | Promise<PluginToolPolicyDecision | void>;
|
|
};
|
|
|
|
export type PluginToolMetadataRegistration = {
|
|
toolName: string;
|
|
displayName?: string;
|
|
description?: string;
|
|
risk?: "low" | "medium" | "high";
|
|
tags?: string[];
|
|
};
|
|
|
|
type PluginControlUiTabGroup = "control" | "agent";
|
|
|
|
export type PluginControlUiDescriptor = {
|
|
id: string;
|
|
/** "tab" adds a sidebar tab; "widget" advertises a trusted dashboard renderer. */
|
|
surface: "session" | "tool" | "run" | "settings" | "tab" | "widget";
|
|
label: string;
|
|
description?: string;
|
|
placement?: string;
|
|
schema?: PluginJsonValue;
|
|
requiredScopes?: OperatorScope[];
|
|
/** Icon name hint for tab descriptors; unknown names fall back to a generic icon. */
|
|
icon?: string;
|
|
/**
|
|
* Gateway HTTP path (e.g. /plugins/<id>/panel) rendered in a sandboxed frame
|
|
* when the Control UI has no bundled view for this tab.
|
|
*/
|
|
path?: string;
|
|
/** Sidebar group for tab descriptors; defaults to "control". */
|
|
group?: PluginControlUiTabGroup;
|
|
/** Sort order among plugin tabs; lower renders first. */
|
|
order?: number;
|
|
};
|
|
|
|
export type PluginSessionActionContext = {
|
|
pluginId: string;
|
|
actionId: string;
|
|
sessionKey?: string;
|
|
agentId?: string;
|
|
payload?: PluginJsonValue;
|
|
client?: {
|
|
connId?: string;
|
|
scopes: string[];
|
|
};
|
|
};
|
|
|
|
export type PluginSessionActionResult =
|
|
| {
|
|
ok?: true;
|
|
result?: PluginJsonValue;
|
|
reply?: PluginJsonValue;
|
|
continueAgent?: boolean;
|
|
}
|
|
| {
|
|
ok: false;
|
|
error: string;
|
|
code?: string;
|
|
details?: PluginJsonValue;
|
|
};
|
|
|
|
export type PluginSessionActionRegistration = {
|
|
id: string;
|
|
description?: string;
|
|
schema?: PluginJsonValue;
|
|
requiredScopes?: OperatorScope[];
|
|
handler: (
|
|
ctx: PluginSessionActionContext,
|
|
) => PluginSessionActionResult | void | Promise<PluginSessionActionResult | void>;
|
|
};
|
|
|
|
export type PluginRuntimeLifecycleRegistration = {
|
|
id: string;
|
|
description?: string;
|
|
cleanup?: (ctx: {
|
|
reason: PluginHostCleanupReason;
|
|
sessionKey?: string;
|
|
runId?: string;
|
|
}) => void | Promise<void>;
|
|
};
|
|
|
|
export type PluginAgentEventSubscriptionRegistration = {
|
|
id: string;
|
|
description?: string;
|
|
streams?: AgentEventStream[];
|
|
handle: (
|
|
event: AgentEventPayload,
|
|
ctx: {
|
|
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Run-context JSON reads are caller-typed by namespace.
|
|
getRunContext: <T extends PluginJsonValue = PluginJsonValue>(
|
|
namespace: string,
|
|
) => T | undefined;
|
|
setRunContext: (namespace: string, value: PluginJsonValue) => void;
|
|
clearRunContext: (namespace?: string) => void;
|
|
},
|
|
) => void | Promise<void>;
|
|
};
|
|
|
|
export type PluginAgentEventEmitParams = {
|
|
runId: string;
|
|
stream: AgentEventStream;
|
|
data: PluginJsonValue;
|
|
sessionKey?: string;
|
|
};
|
|
|
|
export type PluginAgentEventEmitResult =
|
|
| { emitted: true; stream: AgentEventStream }
|
|
| { emitted: false; reason: string };
|
|
|
|
export type PluginRunContextPatch = {
|
|
runId: string;
|
|
namespace: string;
|
|
value?: PluginJsonValue;
|
|
unset?: boolean;
|
|
};
|
|
|
|
export type PluginRunContextGetParams = {
|
|
runId: string;
|
|
namespace: string;
|
|
};
|
|
|
|
export type PluginSessionSchedulerJobRegistration = {
|
|
id: string;
|
|
sessionKey: string;
|
|
kind: string;
|
|
description?: string;
|
|
cleanup?: (ctx: {
|
|
reason: PluginHostCleanupReason;
|
|
sessionKey: string;
|
|
jobId: string;
|
|
}) => void | Promise<void>;
|
|
};
|
|
|
|
export type PluginSessionSchedulerJobHandle = {
|
|
id: string;
|
|
pluginId: string;
|
|
sessionKey: string;
|
|
kind: string;
|
|
};
|
|
|
|
type PluginSessionAttachmentFile = {
|
|
path: string;
|
|
};
|
|
|
|
export type PluginAttachmentChannelHints = {
|
|
parseMode?: "HTML";
|
|
silent?: boolean;
|
|
/** Require host detection to match this MIME before forcing document delivery. */
|
|
forceDocumentMime?: string;
|
|
threadId?: string | number;
|
|
/** @deprecated Put portable attachment hints directly on `channelHints`. */
|
|
telegram?: {
|
|
parseMode?: "HTML";
|
|
disableNotification?: boolean;
|
|
/**
|
|
* Require host-side detection to match this MIME before forcing document delivery.
|
|
* Mismatched files are rejected before the outbound adapter is called.
|
|
*/
|
|
forceDocumentMime?: string;
|
|
};
|
|
/** @deprecated Use `channelHints.threadId`. */
|
|
slack?: {
|
|
threadTs?: string;
|
|
};
|
|
};
|
|
|
|
export type PluginSessionAttachmentCaptionFormat = "plain" | "html" | "markdown";
|
|
|
|
export type PluginSessionAttachmentParams = {
|
|
sessionKey: string;
|
|
files: PluginSessionAttachmentFile[];
|
|
text?: string;
|
|
threadId?: string | number;
|
|
forceDocument?: boolean;
|
|
maxBytes?: number;
|
|
captionFormat?: PluginSessionAttachmentCaptionFormat;
|
|
channelHints?: PluginAttachmentChannelHints;
|
|
};
|
|
|
|
export type PluginSessionAttachmentResult =
|
|
| {
|
|
ok: true;
|
|
channel: string;
|
|
deliveredTo: string;
|
|
count: number;
|
|
}
|
|
| { ok: false; error: string };
|
|
|
|
type PluginSessionTurnScheduleCommonParams = {
|
|
sessionKey: string;
|
|
message: string;
|
|
agentId?: string;
|
|
deliveryMode?: "none" | "announce";
|
|
name?: string;
|
|
/** Optional cleanup tag. Reserved cron-name delimiters like `:` are rejected. */
|
|
tag?: string;
|
|
};
|
|
|
|
export type PluginSessionTurnScheduleParams =
|
|
| ({
|
|
at: string | number | Date;
|
|
deleteAfterRun?: boolean;
|
|
} & PluginSessionTurnScheduleCommonParams)
|
|
| ({
|
|
delayMs: number;
|
|
deleteAfterRun?: boolean;
|
|
} & PluginSessionTurnScheduleCommonParams)
|
|
| ({
|
|
cron: string;
|
|
tz?: string;
|
|
deleteAfterRun?: false;
|
|
} & PluginSessionTurnScheduleCommonParams);
|
|
|
|
export type PluginSessionTurnUnscheduleByTagParams = {
|
|
sessionKey: string;
|
|
tag: string;
|
|
};
|
|
|
|
export type PluginSessionTurnUnscheduleByTagResult = {
|
|
removed: number;
|
|
failed: number;
|
|
};
|
|
|
|
export function normalizePluginHostHookId(value: string | undefined): string {
|
|
return (value ?? "").trim();
|
|
}
|
|
|
|
function normalizeQueuedInjectionText(
|
|
entry: PluginNextTurnInjectionRecord,
|
|
placement: PluginNextTurnInjectionPlacement,
|
|
): string | undefined {
|
|
const candidate = entry as {
|
|
placement?: unknown;
|
|
text?: unknown;
|
|
};
|
|
if (candidate.placement !== placement || typeof candidate.text !== "string") {
|
|
return undefined;
|
|
}
|
|
const text = candidate.text.trim();
|
|
return text || undefined;
|
|
}
|
|
|
|
export function buildPluginAgentTurnPrepareContext(params: {
|
|
queuedInjections: PluginNextTurnInjectionRecord[];
|
|
}): PluginAgentTurnPrepareResult {
|
|
const prepend = params.queuedInjections
|
|
.map((entry) => normalizeQueuedInjectionText(entry, "prepend_context"))
|
|
.filter(Boolean);
|
|
const append = params.queuedInjections
|
|
.map((entry) => normalizeQueuedInjectionText(entry, "append_context"))
|
|
.filter(Boolean);
|
|
return {
|
|
...(prepend.length > 0 ? { prependContext: prepend.join("\n\n") } : {}),
|
|
...(append.length > 0 ? { appendContext: append.join("\n\n") } : {}),
|
|
};
|
|
}
|