Submatter/apps/worker/src/harness.ts
Randall Stillwell 6ccb060bea feat(second-brain): open self-hosted Obsidian for crews — harness-agnostic v0.1
- MCP create-backlog-item for natural language Plan/Epic/Task authoring
- Worker non-code: optional worktree + per-harness allowlist + doc_review→published
- Harness registry: harness_providers/role_harness_routes, WorkerAdapter (minimal|crewai|langgraph|remote-ag-ui), AG-UI POST /api/ag-ui, Settings → Harnesses with Test
- Plans tree: backlog.listTree + createPlan/Epic/Task + Start→Convoy, web CRUD without CLI
- Fleet: /fleet rollup across workspaces + SSE /api/fleet/events

Decision: docs/decisions/2026-08-25-second-brain-os-pivot.md
Convoy: .convoys/second-brain-os-v01

Co-authored-by: orchestrator
2026-08-25 16:20:53 -05:00

94 lines
3.2 KiB
TypeScript

import { ALLOWED_COMMANDS } from "@tasks/shared/worker";
import type { HarnessType } from "@tasks/shared/worker";
export type HarnessConfig = {
alias: string;
harnessType: HarnessType;
needsWorktree: boolean;
allowedCommands: readonly string[] | null;
endpointUrl?: string | null;
authEnvVar?: string | null;
};
const DOC_WRITER: HarnessConfig = {
alias: "doc-writer",
harnessType: "minimal",
needsWorktree: false,
allowedCommands: [],
};
const DEFAULT: HarnessConfig = {
alias: "minimal",
harnessType: "minimal",
needsWorktree: true,
allowedCommands: null,
};
const HARNESS_DEFAULTS: Record<string, Partial<HarnessConfig>> = {
minimal: { harnessType: "minimal", needsWorktree: true, allowedCommands: null },
crewai: { harnessType: "crewai", needsWorktree: true, allowedCommands: null },
langgraph: { harnessType: "langgraph", needsWorktree: true, allowedCommands: null },
"remote-ag-ui": { harnessType: "remote-ag-ui", needsWorktree: false, allowedCommands: [] },
};
const HARNESS_BY_ALIAS: Record<string, HarnessConfig> = {
"doc-writer": DOC_WRITER,
minimal: DEFAULT,
default: DEFAULT,
};
function configForAlias(alias: string): HarnessConfig {
if (HARNESS_BY_ALIAS[alias]) return HARNESS_BY_ALIAS[alias]!;
const def = HARNESS_DEFAULTS[alias] as Partial<HarnessConfig> | undefined;
if (def?.harnessType) {
return {
alias,
harnessType: def.harnessType,
needsWorktree: def.needsWorktree ?? true,
allowedCommands: def.allowedCommands ?? null,
};
}
return { alias, harnessType: "minimal", needsWorktree: true, allowedCommands: null };
}
/**
* Resolve harness config for a job. Brief 3 adds harness_providers + remote-ag-ui.
* - If job carries `harnessAlias` use it (from orchestrator or DB route).
* - Else if roleId itself is a harness alias use it.
* - Else fallback to minimal.
*/
export function resolveHarnessForJob(job: {
roleId: string;
harnessAlias?: string | null;
harnessType?: string | null;
endpointUrl?: string | null;
authEnvVar?: string | null;
}): HarnessConfig {
const alias = job.harnessAlias ?? (HARNESS_BY_ALIAS[job.roleId] ? job.roleId : null);
if (alias) {
const base = configForAlias(alias);
return {
...base,
...(job.harnessType ? { harnessType: job.harnessType as HarnessType } : {}),
...(job.endpointUrl !== undefined ? { endpointUrl: job.endpointUrl } : {}),
...(job.authEnvVar !== undefined ? { authEnvVar: job.authEnvVar } : {}),
};
}
if (job.roleId.includes("doc")) return DOC_WRITER;
return DEFAULT;
}
export function allowedCommandsForHarness(harness: HarnessConfig): readonly string[] | null {
return harness.allowedCommands === null ? ALLOWED_COMMANDS : harness.allowedCommands;
}
/**
* Executor registry — minimal adapter delegates to MinimalSession; remote adapters
* could call AG-UI endpoint. For brief 3 all adapters share the same local
* MinimalSession implementation but are differentiated by harnessType for
* observability and future extension.
*/
export function getHarnessExecutor(alias: string): { id: string; harnessType: HarnessType } {
const cfg = configForAlias(alias);
return { id: cfg.alias, harnessType: cfg.harnessType };
}