Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions common/skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { migrateLegacyInputsToValues, renderValues, ValueSchema } from "./values
* *input*); this is the builder's *output*.
*/

/** Agent architectures a skill can target. Scout and Cowork are enabled today. */
export const SkillArchitecture = z.enum(["scout", "cowork", "copilot-studio"]);
/** Agent architectures a skill can target. Scout, Cowork, and Generic are enabled today. */
export const SkillArchitecture = z.enum(["scout", "cowork", "generic", "copilot-studio"]);
export type SkillArchitecture = z.infer<typeof SkillArchitecture>;

/** UI metadata for the architecture selector (shared so main + renderer agree). */
Expand All @@ -30,6 +30,7 @@ export interface ArchitectureOption {
export const ARCHITECTURES: readonly ArchitectureOption[] = [
{ id: "scout", label: "Scout", enabled: true, note: "Microsoft Scout: native WorkIQ, browser, files, and built-in skills." },
{ id: "cowork", label: "Cowork", enabled: true, note: "Microsoft 365 Copilot (Cowork): native Teams, Outlook, Calendar, SharePoint, files, and built-in skills." },
{ id: "generic", label: "Generic agent", enabled: true, note: "Portable skill with no product-specific tools — shell, files, and HTTP fetch only." },
{ id: "copilot-studio", label: "Copilot Studio", enabled: false, note: "Coming soon." },
] as const;

Expand Down Expand Up @@ -82,6 +83,13 @@ export const TARGETS: readonly BuildTarget[] = [
enabled: true,
note: "An on-demand skill for Microsoft 365 Copilot (Cowork) you export and install.",
},
{
kind: "skill",
architecture: "generic",
label: "Generic agent skill",
enabled: true,
note: "A portable skill with no product-specific tools, for any destination agent.",
},
{
kind: "skill",
architecture: "copilot-studio",
Expand Down
2 changes: 1 addition & 1 deletion electron/skillbuilder/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class SkillBuilder extends AgentBuilder<LiveBuild> {
const { sessionId, architecture, feedback } = input;
if (this.active.has(sessionId)) throw new Error("A build is already running for this session.");
if (!catalogueFor(architecture)) {
throw new Error("That target architecture isn't available yet. Choose Scout or Cowork.");
throw new Error("That target architecture isn't available yet. Choose Scout, Cowork, or Generic agent.");
}
const analysis = loadPersistedAnalysis(sessionId);
if (!analysis) throw new Error("There is no analysis for this recording yet.");
Expand Down
98 changes: 98 additions & 0 deletions electron/skillbuilder/generic-catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* A **static, versioned** snapshot of the "generic agent" target's capabilities —
* unlike {@link ../skillbuilder/scout-catalog.ts} and {@link ../skillbuilder/cowork-catalog.ts},
* this is deliberately NOT a catalogue of one product's internal tool IDs. It exists so a
* recording can be turned into a portable `SKILL.md` that doesn't assume the destination
* agent has `workiq_*`, `m365_*`, or any other product-specific tool — only the common-
* denominator primitives most coding/ops agents ship with (a shell, file read/write/search,
* and HTTP fetch).
*
* IMPORTANT: no product-specific tool names here, ever. If a future primitive is added,
* it must be something broadly available across agents (not one vendor's API), or it
* belongs in a product-specific catalogue instead.
*/
export const GENERIC_CATALOGUE_VERSION = "2026-08-03";

/**
* The reusable core of the generic catalogue: universal primitives and a recorded-action→
* primitive mapping, with no reference to any single agent's internal tool IDs.
*/
export const GENERIC_NATIVE_CAPABILITIES = `
## Where this runs, and what to assume

The destination agent is UNKNOWN — it could be Claude Code, Copilot CLI, or any other
coding/ops agent. Do not assume any product-specific tool exists. Only rely on the
common-denominator primitives every such agent has in some form:

1. **A shell.** The agent can run shell commands and whatever CLIs the user has installed
(\`git\`, \`gh\`, \`az\`/\`aws\`/\`gcloud\`, \`npm\`, \`docker\`, etc.). Prefer a first-class CLI or
API over driving a web UI. Describe commands in plain POSIX shell; call out where a
Windows/PowerShell equivalent would differ if the task is platform-sensitive.
2. **Local file read / write / search.** The agent can read a file, write or edit a file,
search file contents, and find files by name/pattern. Describe these as plain actions
("read <file>", "search <dir> for <pattern>") rather than naming a specific tool call.
3. **HTTP fetch.** The agent can fetch a URL's contents. Use this for reading public pages
or calling a documented HTTP API — describe it as "fetch <url>", not a named tool.
4. **Browser UI automation — LAST RESORT, and never assumed.** Some agents can drive a web
UI (click, type, navigate); many cannot. Only fall back to a UI-driven step when the task
has no API and no CLI, and explicitly flag it as "requires browser automation, if the
agent supports it" so the skill degrades gracefully (skip/ask the user) on an agent that
doesn't.

Do NOT reference: \`workiq_*\`, \`m365_*\`/\`outlook*\`/\`sharepoint*\`, \`pbi_*\`, named
\`browser_*\` tool suites, or any other vendor-specific tool ID. If the recording used one of
those (e.g. it recorded a Teams or SharePoint action), translate it to the closest universal
primitive above (an API/CLI call if one exists, otherwise a flagged manual/UI step) rather
than naming the product tool that happened to be recorded.

## Recorded action → universal primitive (examples)

| Recording shows | Prefer |
| --- | --- |
| Opening / reading a local file or folder | plain file read / directory listing |
| Editing or creating a local file | plain file write/edit |
| Searching file contents or finding files by name | plain content search / filename search |
| Reading a public web page or calling a documented API | HTTP fetch of the URL/endpoint |
| Acting on GitHub — issues, PRs, releases, repos | the \`gh\` CLI via the shell (\`gh issue\`, \`gh pr\`, \`gh api\`) |
| Running git, cloud, or package operations | the matching CLI via the shell (\`git\`, \`az\`/\`aws\`/\`gcloud\`, \`npm\`, \`docker\`) |
| Acting inside Teams, Outlook, SharePoint, or another product-specific surface | no universal primitive covers this — call it out as "requires <product>'s own tool/API on the destination agent" rather than naming the recorded tool |
| Filling a form on a web app with no API or CLI | flag as a browser-automation step, "if the agent supports it" — do not assume it does |
`.trim();

const GENERIC_CATALOGUE = `
# Target: Generic agent — portable, tool-agnostic capability catalogue

A generic-target **skill** is a \`SKILL.md\` file: YAML frontmatter followed by a markdown
**instructions body**, written so it can be dropped into any agent's skills directory
without edits.

Frontmatter fields:
- \`name\` — kebab-case, \`^[a-z0-9-]+$\`.
- \`description\` — one line of trigger keywords (when an agent should reach for this skill).
- \`allowed-tools\` (optional) — a YAML list of broadly-recognized permission-scoping patterns,
e.g. \`Bash(git *)\`, \`Read\`, \`Write\`, \`Grep\`, \`Glob\`. These names are conventional across
several agents; an agent that doesn't recognize \`allowed-tools\` simply ignores it, so it's
safe to include but never required for the skill to be understood.

The body is plain instructions written TO whichever agent loads it (imperative voice): when
to use the skill, the procedure to follow, and how to handle inputs and edge cases — using
only the universal primitives below, never a named product tool.

${GENERIC_NATIVE_CAPABILITIES}

## Writing the SKILL.md body

- Write a GENERALIZED procedure: if the recording acted on N specific items, the body loops
over ALL items of that kind, not the specific examples that were recorded.
- Resolve each input via the plan (a fixed value / the user provides it / the agent locates it).
- Describe every step in plain imperative language using only the universal primitives above
— no \`workiq_*\`, no \`m365_*\`, no named browser-tool suite, no other vendor-specific tool ID.
- If a recorded step has no universal-primitive equivalent (e.g. a Teams/SharePoint/Outlook
action), say so explicitly in the body rather than inventing or naming a tool for it.
- Keep it concise and imperative. Include a short "When to use" and the ordered steps.
`.trim();

/** The generic catalogue. Exposed so {@link catalogueFor} can dispatch on it. */
export function genericCatalogue(): string {
return GENERIC_CATALOGUE;
}
3 changes: 3 additions & 0 deletions electron/skillbuilder/scout-catalog.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { SkillArchitecture } from "../../common/skill";
import { coworkCatalogue } from "./cowork-catalog";
import { genericCatalogue } from "./generic-catalog";

/**
* A **static, versioned** snapshot of the target agent's native capabilities,
Expand Down Expand Up @@ -123,6 +124,8 @@ export function catalogueFor(architecture: SkillArchitecture): string | null {
return SCOUT_CATALOGUE;
case "cowork":
return coworkCatalogue();
case "generic":
return genericCatalogue();
default:
return null;
}
Expand Down
27 changes: 27 additions & 0 deletions evals/skillbuilder/scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,32 @@ const githubIssueTriage: SkillBuilderScenario = {
},
};

/** Same recording as githubIssueTriage, targeting the Generic-agent catalogue: must still reach
* for the gh CLI (a universal primitive — the shell), but the plan must name NO product-specific
* tool ID (no workiq_*, no m365_*, no named browser-tool suite) since the destination agent is
* unknown. */
const githubIssueTriageGeneric: SkillBuilderScenario = {
...githubIssueTriage,
id: "github-issue-triage-generic",
title: "Triage new bug issues on GitHub (generic agent)",
architecture: "generic",
rubric: {
mustUseAny: [["gh "], ["gh issue", "gh api"]],
forbidden: [
"playwright",
"browser_",
"workiq",
"m365_",
"click",
"navigate to github",
"github.com/acme",
],
minValues: 1,
minCalculations: 1,
minActions: 1,
},
};

/* --- Cowork (Microsoft 365 Copilot) scenarios ----------------------------- */

/** Read a Teams channel in the web app, summarize, post a digest — must use the m365_teams tool, never the browser. */
Expand Down Expand Up @@ -399,6 +425,7 @@ const coworkCalendarSchedule: SkillBuilderScenario = {
export const skillScenarios: SkillBuilderScenario[] = [
priceTracker,
githubIssueTriage,
githubIssueTriageGeneric,
coworkTeamsDigest,
coworkOutlookReply,
coworkCalendarSchedule,
Expand Down
7 changes: 4 additions & 3 deletions src/Library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -961,9 +961,10 @@ function SkillBuilderView({
setBuiltName(s.name);
setExportedPath(s.exportedPath);
setArchitecture(s.architecture);
// We don't persist how it was placed; Cowork can only export, and Scout defaults
// to install (its primary action), so infer from the architecture on reopen.
setPlacement(s.architecture === "cowork" ? "export" : "install");
// We don't persist how it was placed; only Scout has a live skills folder to
// install into (its primary action) — every other target (Cowork, Generic) can
// only export, so infer from the architecture on reopen.
setPlacement(s.architecture === "scout" ? "install" : "export");
if (s.plan) setPlan(s.plan);
setPhase("done");
} else if (hasSkill) {
Expand Down