diff --git a/packages/gateway-protocol/src/schema/agents-models-skills.ts b/packages/gateway-protocol/src/schema/agents-models-skills.ts index 0233181bd84..49540a958d0 100644 --- a/packages/gateway-protocol/src/schema/agents-models-skills.ts +++ b/packages/gateway-protocol/src/schema/agents-models-skills.ts @@ -568,6 +568,16 @@ const SkillProposalTargetSchema = Type.Object( { additionalProperties: false }, ); +const SkillProposalOriginSchema = Type.Object( + { + agentId: Type.Optional(NonEmptyString), + sessionKey: Type.Optional(NonEmptyString), + runId: Type.Optional(NonEmptyString), + messageId: Type.Optional(NonEmptyString), + }, + { additionalProperties: false }, +); + const SkillProposalRecordSchema = Type.Object( { schema: Type.Literal("openclaw.skill-workshop.proposal.v1"), @@ -579,6 +589,7 @@ const SkillProposalRecordSchema = Type.Object( createdAt: NonEmptyString, updatedAt: NonEmptyString, createdBy: SkillProposalSourceSchema, + origin: Type.Optional(SkillProposalOriginSchema), proposedVersion: NonEmptyString, draftFile: Type.Literal("PROPOSAL.md"), draftHash: NonEmptyString, diff --git a/src/skills/workshop/service.ts b/src/skills/workshop/service.ts index 4a44c6062ea..20fdd55d53a 100644 --- a/src/skills/workshop/service.ts +++ b/src/skills/workshop/service.ts @@ -49,6 +49,7 @@ import { type SkillProposalActionInput, type SkillProposalApplyResult, type SkillProposalCreateInput, + type SkillProposalOrigin, type SkillProposalManifest, type SkillProposalReadResult, type SkillProposalRecord, @@ -161,6 +162,24 @@ function decodeProposalTextFile(buffer: Buffer, label: string): string { return content; } +function normalizeProposalOrigin( + origin: SkillProposalOrigin | undefined, +): SkillProposalOrigin | undefined { + const agentId = normalizeOptionalString(origin?.agentId); + const sessionKey = normalizeOptionalString(origin?.sessionKey); + const runId = normalizeOptionalString(origin?.runId); + const messageId = normalizeOptionalString(origin?.messageId); + if (!agentId && !sessionKey && !runId && !messageId) { + return undefined; + } + return { + ...(agentId ? { agentId } : {}), + ...(sessionKey ? { sessionKey } : {}), + ...(runId ? { runId } : {}), + ...(messageId ? { messageId } : {}), + }; +} + export async function inspectSkillProposal( proposalId: string, options: SkillProposalScopeOptions = {}, @@ -242,6 +261,7 @@ export async function proposeCreateSkill( const id = createSkillProposalId(name); const goal = normalizeOptionalString(input.goal); const evidence = normalizeOptionalString(input.evidence); + const origin = normalizeProposalOrigin(input.origin); const record: SkillProposalRecord = { schema: SKILL_WORKSHOP_SCHEMA, id, @@ -252,6 +272,7 @@ export async function proposeCreateSkill( createdAt: now, updatedAt: now, createdBy: input.createdBy ?? "skill-workshop", + ...(origin ? { origin } : {}), proposedVersion: "v1", draftFile: "PROPOSAL.md", draftHash: hashSkillProposalContent(proposalContent), @@ -313,6 +334,7 @@ export async function proposeUpdateSkill( const id = createSkillProposalId(targetSkill.skillKey || targetSkill.name); const goal = normalizeOptionalString(input.goal); const evidence = normalizeOptionalString(input.evidence); + const origin = normalizeProposalOrigin(input.origin); const record: SkillProposalRecord = { schema: SKILL_WORKSHOP_SCHEMA, id, @@ -323,6 +345,7 @@ export async function proposeUpdateSkill( createdAt: now, updatedAt: now, createdBy: input.createdBy ?? "skill-workshop", + ...(origin ? { origin } : {}), proposedVersion: "v1", draftFile: "PROPOSAL.md", draftHash: hashSkillProposalContent(proposalContent), diff --git a/src/skills/workshop/store.ts b/src/skills/workshop/store.ts index 83bef699372..22afb66c384 100644 --- a/src/skills/workshop/store.ts +++ b/src/skills/workshop/store.ts @@ -627,6 +627,7 @@ function parseSkillProposalRecord(raw: unknown): SkillProposalRecord | null { typeof record.updatedAt !== "string" || typeof record.draftHash !== "string" || record.draftFile !== PROPOSAL_DRAFT_FILE || + !isValidProposalOrigin(record.origin) || !isValidSupportFileList(record.supportFiles) || !record.target || typeof record.target !== "object" || @@ -642,6 +643,23 @@ function parseSkillProposalRecord(raw: unknown): SkillProposalRecord | null { return record; } +function isValidProposalOrigin(value: unknown): boolean { + if (value === undefined) { + return true; + } + if (!value || typeof value !== "object" || Array.isArray(value)) { + return false; + } + const origin = value as Record; + for (const key of ["agentId", "sessionKey", "runId", "messageId"]) { + const item = origin[key]; + if (item !== undefined && typeof item !== "string") { + return false; + } + } + return true; +} + function isValidSupportFileList(value: unknown): boolean { if (value === undefined) { return true; diff --git a/src/skills/workshop/types.ts b/src/skills/workshop/types.ts index 8775ac801c3..c0f18eb6d3c 100644 --- a/src/skills/workshop/types.ts +++ b/src/skills/workshop/types.ts @@ -11,6 +11,13 @@ export type SkillProposalStatus = "pending" | "applied" | "rejected" | "quaranti export type SkillProposalScannerState = "pending" | "clean" | "failed" | "quarantined"; export type SkillProposalSource = "skill-workshop" | "cli" | "gateway"; +export type SkillProposalOrigin = { + agentId?: string; + sessionKey?: string; + runId?: string; + messageId?: string; +}; + export type SkillProposalScan = { state: SkillProposalScannerState; scannedAt: string; @@ -47,6 +54,7 @@ export type SkillProposalRecord = { createdAt: string; updatedAt: string; createdBy: SkillProposalSource; + origin?: SkillProposalOrigin; proposedVersion: string; draftFile: "PROPOSAL.md"; draftHash: string; @@ -110,6 +118,7 @@ export type SkillProposalCreateInput = { content: string; supportFiles?: SkillProposalSupportFileInput[]; createdBy?: SkillProposalSource; + origin?: SkillProposalOrigin; goal?: string; evidence?: string; }; @@ -122,6 +131,7 @@ export type SkillProposalUpdateInput = { content: string; supportFiles?: SkillProposalSupportFileInput[]; createdBy?: SkillProposalSource; + origin?: SkillProposalOrigin; goal?: string; evidence?: string; };