Initial Coworker app scaffold
Some checks failed
Codex Template Compliance / compliance (push) Successful in 7s
Release Dry Run / release-dry-run (push) Failing after 44s
Build / build (push) Failing after 45s

This commit is contained in:
2026-06-19 01:07:16 +02:00
commit 2ec18964b7
46 changed files with 2582 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
{
"name": "@coworker/core",
"version": "0.1.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json"
}
}

View File

@@ -0,0 +1,60 @@
import { AgentRunRequest, AgentRunResult, ChatMessage } from "./types";
import { recallBrain } from "./brain";
import { createId, nowIso } from "./ids";
export async function runAgent(
request: AgentRunRequest,
completion: (messages: ChatMessage[]) => Promise<string>
): Promise<AgentRunResult> {
const recalled = recallBrain(request.brain, request.prompt, 8);
const systemContent = [
"You are Coworker, a local-first coding assistant.",
"Show proposed file changes as unified diffs and wait for user approval before writes.",
request.workspace ? `Current workspace: ${request.workspace.name}` : "No workspace is open.",
recalled.length > 0
? `Recalled Brain items:\n${recalled.map((r) => `- ${r.item.title}: ${r.item.body}`).join("\n")}`
: "No Brain items were recalled."
].join("\n\n");
const messages: ChatMessage[] = [
{ id: createId("msg"), role: "system", content: systemContent, createdAt: nowIso() },
...request.messages,
{ id: createId("msg"), role: "user", content: request.prompt, createdAt: nowIso() }
];
const content = await completion(messages);
return {
assistantMessage: {
id: createId("msg"),
role: "assistant",
content,
createdAt: nowIso()
},
recalled,
proposedDiffs: extractDiffs(content),
toolLog: [
{
id: createId("log"),
level: "info",
message: `Recalled ${recalled.length} Brain item(s).`,
createdAt: nowIso()
}
]
};
}
export function extractDiffs(content: string) {
const blocks = [...content.matchAll(/```(?:diff|patch)\n([\s\S]*?)```/g)];
return blocks.map((block, index) => ({
id: createId("diff"),
filePath: inferDiffFile(block[1]) ?? `proposed-change-${index + 1}.patch`,
title: `Proposed change ${index + 1}`,
patch: block[1].trim(),
applied: false
}));
}
function inferDiffFile(patch: string): string | undefined {
const match = patch.match(/^\+\+\+\s+b\/(.+)$/m);
return match?.[1]?.trim();
}

View File

@@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";
import { createBrainItem, recallBrain } from "./brain";
describe("Brain recall", () => {
it("returns matching pinned and semantic keyword items", () => {
const items = [
createBrainItem({
kind: "memory",
scope: "global",
title: "Model preference",
body: "User prefers local Ollama models for coding.",
pinned: true
}),
createBrainItem({
kind: "skill",
scope: "project",
title: "Diff workflow",
body: "Always present patches before writing files."
})
];
const recalled = recallBrain(items, "Use ollama and patches");
expect(recalled.map((r) => r.item.title)).toContain("Model preference");
expect(recalled.map((r) => r.item.title)).toContain("Diff workflow");
});
});

View File

@@ -0,0 +1,76 @@
import { BrainItem, BrainItemKind, BrainRecall, BrainScope } from "./types";
import { createId, nowIso } from "./ids";
export interface BrainInput {
kind: BrainItemKind;
scope: BrainScope;
title: string;
body: string;
tags?: string[];
projectId?: string;
sessionId?: string;
pinned?: boolean;
}
export function createBrainItem(input: BrainInput): BrainItem {
const createdAt = nowIso();
return {
id: createId(input.kind),
kind: input.kind,
scope: input.scope,
projectId: input.projectId,
sessionId: input.sessionId,
title: input.title.trim(),
body: input.body.trim(),
tags: normalizeTags(input.tags ?? []),
pinned: Boolean(input.pinned),
createdAt,
updatedAt: createdAt
};
}
export function updateBrainItem(item: BrainItem, patch: Partial<BrainInput>): BrainItem {
return {
...item,
title: patch.title === undefined ? item.title : patch.title.trim(),
body: patch.body === undefined ? item.body : patch.body.trim(),
tags: patch.tags === undefined ? item.tags : normalizeTags(patch.tags),
pinned: patch.pinned === undefined ? item.pinned : Boolean(patch.pinned),
updatedAt: nowIso()
};
}
export function recallBrain(items: BrainItem[], query: string, limit = 8): BrainRecall[] {
const terms = tokenize(query);
if (terms.length === 0) {
return items.filter((item) => item.pinned).slice(0, limit).map((item) => ({
item,
score: 1,
matchedTerms: ["pinned"]
}));
}
return items
.map((item) => {
const haystack = tokenize(`${item.title} ${item.body} ${item.tags.join(" ")}`);
const matchedTerms = terms.filter((term) => haystack.includes(term));
const pinBoost = item.pinned ? 0.5 : 0;
const skillBoost = item.kind === "skill" ? 0.2 : 0;
return {
item,
score: matchedTerms.length + pinBoost + skillBoost,
matchedTerms
};
})
.filter((recall) => recall.score > 0)
.sort((a, b) => b.score - a.score || b.item.updatedAt.localeCompare(a.item.updatedAt))
.slice(0, limit);
}
export function normalizeTags(tags: string[]): string[] {
return Array.from(new Set(tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean))).sort();
}
export function tokenize(text: string): string[] {
return Array.from(new Set(text.toLowerCase().match(/[a-z0-9_äöüß-]{2,}/gi) ?? []));
}

9
packages/core/src/ids.ts Normal file
View File

@@ -0,0 +1,9 @@
export function createId(prefix: string): string {
const random = Math.random().toString(36).slice(2, 10);
const time = Date.now().toString(36);
return `${prefix}_${time}_${random}`;
}
export function nowIso(): string {
return new Date().toISOString();
}

View File

@@ -0,0 +1,6 @@
export * from "./agent";
export * from "./brain";
export * from "./ids";
export * from "./pairing";
export * from "./providers";
export * from "./types";

View File

@@ -0,0 +1,30 @@
import { PairedDevice, PairingChallenge } from "./types";
import { createId, nowIso } from "./ids";
export function createPairingChallenge(ttlMs = 5 * 60 * 1000): PairingChallenge {
const code = String(Math.floor(100000 + Math.random() * 900000));
return {
id: createId("pair"),
code,
expiresAt: new Date(Date.now() + ttlMs).toISOString()
};
}
export async function createPairedDevice(
name: string,
token: string,
digest: (value: string) => Promise<string>
): Promise<PairedDevice> {
return {
id: createId("device"),
name: name.trim() || "Paired device",
tokenHash: await digest(token),
createdAt: nowIso()
};
}
export function isPairingChallengeValid(challenge: PairingChallenge | undefined, code: string): boolean {
if (!challenge) return false;
if (challenge.code !== code.trim()) return false;
return Date.parse(challenge.expiresAt) > Date.now();
}

View File

@@ -0,0 +1,65 @@
import { ModelProvider, ProviderProbeResult } from "./types";
import { createId, nowIso } from "./ids";
export function createProvider(
kind: ModelProvider["kind"],
name: string,
baseUrl: string
): ModelProvider {
const createdAt = nowIso();
return {
id: createId("provider"),
name: name.trim(),
kind,
baseUrl: normalizeBaseUrl(baseUrl),
enabled: true,
supportsTools: kind !== "ollama",
contextWindow: 8192,
defaults: {},
createdAt,
updatedAt: createdAt
};
}
export function normalizeBaseUrl(baseUrl: string): string {
return baseUrl.trim().replace(/\/+$/, "");
}
export async function probeOpenAiCompatibleProvider(
provider: ModelProvider,
fetchImpl: typeof fetch = fetch
): Promise<ProviderProbeResult> {
const url = `${normalizeBaseUrl(provider.baseUrl)}/models`;
try {
const response = await fetchImpl(url, {
headers: provider.apiKey ? { Authorization: `Bearer ${provider.apiKey}` } : undefined
});
if (!response.ok) {
return {
ok: false,
providerId: provider.id,
models: [],
message: `Provider returned HTTP ${response.status}`
};
}
const body = await response.json() as { data?: Array<{ id?: string }>; models?: string[] };
const models = Array.isArray(body.data)
? body.data.map((model) => model.id).filter(Boolean) as string[]
: Array.isArray(body.models)
? body.models
: [];
return {
ok: models.length > 0,
providerId: provider.id,
models,
message: models.length > 0 ? "Provider is reachable." : "Provider responded without model ids."
};
} catch (error) {
return {
ok: false,
providerId: provider.id,
models: [],
message: error instanceof Error ? error.message : "Provider probe failed."
};
}
}

129
packages/core/src/types.ts Normal file
View File

@@ -0,0 +1,129 @@
export type ModelProviderKind = "ollama" | "lmstudio" | "openai-compatible";
export type ModelPurpose = "chat" | "coding" | "reasoning" | "embedding";
export interface ModelProvider {
id: string;
name: string;
kind: ModelProviderKind;
baseUrl: string;
apiKey?: string;
enabled: boolean;
supportsTools: boolean;
contextWindow: number;
defaults: Partial<Record<ModelPurpose, string>>;
createdAt: string;
updatedAt: string;
}
export interface ProviderProbeResult {
ok: boolean;
providerId: string;
models: string[];
message: string;
}
export type BrainScope = "global" | "project" | "session";
export type BrainItemKind = "memory" | "skill";
export interface BrainItem {
id: string;
kind: BrainItemKind;
scope: BrainScope;
projectId?: string;
sessionId?: string;
title: string;
body: string;
tags: string[];
pinned: boolean;
createdAt: string;
updatedAt: string;
}
export interface BrainRecall {
item: BrainItem;
score: number;
matchedTerms: string[];
}
export interface ChatMessage {
id: string;
role: "system" | "user" | "assistant" | "tool";
content: string;
createdAt: string;
}
export interface WorkspaceFile {
path: string;
type: "file" | "directory";
size?: number;
modifiedAt?: string;
}
export interface WorkspaceState {
id: string;
name: string;
rootPath: string;
openedAt: string;
}
export interface AgentRunRequest {
prompt: string;
workspace?: WorkspaceState;
provider?: ModelProvider;
model?: string;
messages: ChatMessage[];
brain: BrainItem[];
}
export interface AgentRunResult {
assistantMessage: ChatMessage;
recalled: BrainRecall[];
proposedDiffs: ProposedDiff[];
toolLog: ToolLogEntry[];
}
export interface ProposedDiff {
id: string;
filePath: string;
title: string;
patch: string;
applied: boolean;
}
export interface ToolLogEntry {
id: string;
level: "info" | "warning" | "error";
message: string;
createdAt: string;
}
export interface WebAccessSettings {
enabled: boolean;
bindHost: "127.0.0.1" | "0.0.0.0";
port: number;
requirePairing: boolean;
}
export interface PairedDevice {
id: string;
name: string;
tokenHash: string;
createdAt: string;
lastSeenAt?: string;
revokedAt?: string;
}
export interface PairingChallenge {
id: string;
code: string;
expiresAt: string;
}
export interface AppState {
providers: ModelProvider[];
brain: BrainItem[];
workspaces: WorkspaceState[];
webAccess: WebAccessSettings;
pairedDevices: PairedDevice[];
}

View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": false,
"noEmit": false,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}