Skip to content
Open
22 changes: 15 additions & 7 deletions packages/core/src/chat/marketMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
//
// Direction is in the name: *Request = UI -> host; *Event = host -> UI.

import type { Note, Reputation } from "../core/types.js";
import type { MarketItemType, Note, PluginEngine, PluginManifest, Reputation } from "../core/types.js";

/** One item row as the UI renders it (cards + detail). Mirrors the subset of `Skill`
* the UI needs — kept here so host (env callbacks) and UI agree. Covers both kinds:
* `type` splits the Skills / Workflows tabs; `requiredSkills` (workflows only) are
* the prerequisite skill mint ids the detail view renders as clickable links. */
* the UI needs — kept here so host (env callbacks) and UI agree. Covers all marketplace
* kinds: `type` identifies skills, workflows, and future plugin cards. `requiredSkills`
* is workflow-only; plugin cards use engine/provenance fields for later badges/details. */
export interface SkillCard {
id: string; // mint address
type?: "skill" | "workflow";
type?: MarketItemType;
name: string;
description?: string;
category?: string;
Expand All @@ -28,6 +28,12 @@ export interface SkillCard {
price?: string; // lamports as decimal string (for buy-all cost summing)
creator?: string; // wallet (paid on a priced buy)
requiredSkills?: string[]; // workflows only: prerequisite skill mint ids
engines?: string[]; // plugins only: engine badges such as "claude", "codex", "mcp"
iqGitPda?: string; // plugins only: canonical IQ Git provenance anchor
version?: string;
capabilities?: string[];
permissions?: string[];
pluginManifest?: PluginManifest;
}

/** Full agent profile payload sent to the UI on getAgentProfile. */
Expand Down Expand Up @@ -71,10 +77,11 @@ export interface RpcStatus {

// ── UI -> host (requests) ───────────────────────────────────────────────────
export type MarketRequest =
| { type: "searchSkills"; query: string; kind?: "skill" | "workflow" } // kind = the active tab
| { type: "searchSkills"; query: string; kind?: MarketItemType } // kind = the active tab / future plugin tab
| { type: "getSkillDetail"; mint: string } // open the detail view for one item
| { type: "getSkillDoc"; name: string } // read an installed skill's local SKILL.md by name
| { type: "buySkill"; skillId: string; creatorWallet?: string }
| { type: "installPlugin"; pluginId: string; engine: PluginEngine }
| { type: "disposeSkill"; skillId: string } // un-equip an owned skill (local + sticky)
| { type: "reEquipSkill"; skillId: string } // undo a dispose (re-install, no re-buy)
| { type: "ownedSkills" } // ask the host to (re)send the owned list
Expand All @@ -83,7 +90,7 @@ export type MarketRequest =
| { type: "useDefaultRpc" } // clear any key, fall back to the default
| { type: "getRpcStatus" } // ask the host to (re)send rpcStatus
// issue #34: post a comment on a skill (holder-gated client-side)
| { type: "postNote"; skillId: string; skillType?: "skill" | "workflow"; text: string; gitLink?: string }
| { type: "postNote"; skillId: string; skillType?: MarketItemType; text: string; gitLink?: string }
// issue #35: agent directory + profile
| { type: "listAgents" }
| { type: "getAgentProfile"; wallet: string }
Expand All @@ -110,6 +117,7 @@ export type MarketEvent =
| { type: "skillDetail"; detail: SkillDetail } // full detail for the opened item (includes notes)
| { type: "skillDoc"; name: string; text: string | null } // installed skill's SKILL.md (null = not found)
| { type: "buyResult"; skillId: string; ok: boolean; slug?: string; error?: string }
| { type: "pluginInstallResult"; pluginId: string; engine: PluginEngine; ok: boolean; error?: string }
| { type: "disposeResult"; skillId: string; ok: boolean; slug?: string; error?: string }
| { type: "reEquipResult"; skillId: string; ok: boolean; slug?: string; error?: string }
// installed skill names (panel fill) + slug->mint for bought NFTs (reuse market detail) +
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/chat/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import type { AgentRuntime, SessionHandle } from "../runtime/contract.js";
import type { ApprovalChannel } from "../runtime/approval/channel.js";
import type { MarketItemType, PluginEngine } from "../core/types.js";
import type { SkillCard, MarketRequest } from "./marketMessages.js";

// The two-way pipe to ONE chat UI (one panel / one socket). Messages both ways are
Expand Down Expand Up @@ -45,18 +46,19 @@ export interface ChatEnv {
// are host-held (the extension owns them), so they're delegated like wallet/cloud.
// buySkill installs the bought skill's SKILL.md into the runtime skills dir as part
// of the buy (the host calls SkillSync.installBought), returning the installed slug.
searchSkills?(query: string, kind?: "skill" | "workflow"): Promise<SkillCard[]>;
searchSkills?(query: string, kind?: MarketItemType): Promise<SkillCard[]>;
getSkillDetail?(mint: string): Promise<import("./marketMessages.js").SkillDetail>;
getSkillDoc?(name: string): Promise<string | null>;
buySkill?(skillId: string, creatorWallet?: string): Promise<{ ok: boolean; slug?: string; error?: string }>;
installPlugin?(pluginId: string, engine: PluginEngine): Promise<{ ok: boolean; error?: string }>;
// dispose (un-equip) an owned skill: local + sticky (soulbound NFT stays owned, no refund).
disposeSkill?(skillId: string): Promise<{ ok: boolean; slug?: string; error?: string }>;
// re-equip a previously-disposed skill the wallet still owns (undo, no re-buy).
reEquipSkill?(skillId: string): Promise<{ ok: boolean; slug?: string; error?: string }>;
// slug -> mint for disposed (un-pinned) skills — the UI greys these + offers Re-equip.
disposedSkillMints?(): Promise<Record<string, string>>;
// issue #34: post a comment on a skill (holder-gated), returns refreshed notes on success
postNote?(skillId: string, skillType: "skill" | "workflow" | undefined, text: string, gitLink?: string): Promise<{ ok: boolean; notes?: import("./marketMessages.js").Note[]; error?: string }>;
postNote?(skillId: string, skillType: MarketItemType | undefined, text: string, gitLink?: string): Promise<{ ok: boolean; notes?: import("./marketMessages.js").Note[]; error?: string }>;
ownedSkills?(): Promise<string[]>; // skill names already installed (panel fill)
// The wallet's soulbound NFT skills, by display name (catalog ∩ holdings). This is
// what the "Equipped skills" panel shows: skills you OWN on-chain, not local dirs.
Expand Down Expand Up @@ -399,6 +401,12 @@ export function createChatSession(
}
break;
}
case "installPlugin": {
const req = m as Extract<MarketRequest, { type: "installPlugin" }>;
const res = env.installPlugin ? await env.installPlugin(req.pluginId, req.engine) : { ok: false, error: "plugin install unavailable" };
sendMarket({ type: "pluginInstallResult", pluginId: req.pluginId, engine: req.engine, ...res });
break;
}
case "disposeSkill": {
const req = m as Extract<MarketRequest, { type: "disposeSkill" }>;
const res = env.disposeSkill ? await env.disposeSkill(req.skillId) : { ok: false, error: "dispose unavailable" };
Expand Down
Loading