Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18442a1637 | ||
|
|
d076187f0a | ||
|
|
8a5f61724d | ||
|
|
3f557e593c | ||
|
|
284fafad11 | ||
|
|
884a3addf8 |
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -74,13 +74,13 @@
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"oh-my-opencode-darwin-arm64": "3.5.4",
|
||||
"oh-my-opencode-darwin-x64": "3.5.4",
|
||||
"oh-my-opencode-linux-arm64": "3.5.4",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.5.4",
|
||||
"oh-my-opencode-linux-x64": "3.5.4",
|
||||
"oh-my-opencode-linux-x64-musl": "3.5.4",
|
||||
"oh-my-opencode-windows-x64": "3.5.4"
|
||||
"oh-my-opencode-darwin-arm64": "3.5.5",
|
||||
"oh-my-opencode-darwin-x64": "3.5.5",
|
||||
"oh-my-opencode-linux-arm64": "3.5.5",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.5.5",
|
||||
"oh-my-opencode-linux-x64": "3.5.5",
|
||||
"oh-my-opencode-linux-x64-musl": "3.5.5",
|
||||
"oh-my-opencode-windows-x64": "3.5.5"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@ast-grep/cli",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-darwin-arm64",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (darwin-arm64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-darwin-x64",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (darwin-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-arm64-musl",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-arm64",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-arm64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-x64-musl",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-x64",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-windows-x64",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Platform-specific binary for oh-my-opencode (windows-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -247,7 +247,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
"writing": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -314,7 +314,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
"writing": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -372,6 +372,7 @@ exports[`generateModelConfig single native provider uses Gemini models when only
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
@@ -432,6 +433,7 @@ exports[`generateModelConfig single native provider uses Gemini models with isMa
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
@@ -505,6 +507,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
@@ -579,6 +582,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
@@ -652,6 +656,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "opencode/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "opencode/gemini-3-flash",
|
||||
@@ -726,6 +731,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "opencode/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "opencode/gemini-3-flash",
|
||||
@@ -799,6 +805,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "github-copilot/gemini-3-pro-preview",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "github-copilot/gemini-3-flash-preview",
|
||||
@@ -873,6 +880,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "github-copilot/gemini-3-pro-preview",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "github-copilot/gemini-3-flash-preview",
|
||||
@@ -927,10 +935,10 @@ exports[`generateModelConfig fallback providers uses ZAI model for librarian whe
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "zai-coding-plan/glm-4.7",
|
||||
"model": "zai-coding-plan/glm-5",
|
||||
},
|
||||
"writing": {
|
||||
"model": "zai-coding-plan/glm-4.7",
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -982,10 +990,10 @@ exports[`generateModelConfig fallback providers uses ZAI model for librarian wit
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "zai-coding-plan/glm-4.7",
|
||||
"model": "zai-coding-plan/glm-5",
|
||||
},
|
||||
"writing": {
|
||||
"model": "zai-coding-plan/glm-4.7",
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1056,6 +1064,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "opencode/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "opencode/gemini-3-flash",
|
||||
@@ -1129,6 +1138,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "github-copilot/gemini-3-pro-preview",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "github-copilot/gemini-3-flash-preview",
|
||||
@@ -1189,8 +1199,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combinat
|
||||
"model": "anthropic/claude-sonnet-4-5",
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "anthropic/claude-opus-4-6",
|
||||
"variant": "max",
|
||||
"model": "zai-coding-plan/glm-5",
|
||||
},
|
||||
"writing": {
|
||||
"model": "anthropic/claude-sonnet-4-5",
|
||||
@@ -1256,6 +1265,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
@@ -1329,6 +1339,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "github-copilot/gemini-3-pro-preview",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "github-copilot/gemini-3-flash-preview",
|
||||
@@ -1402,6 +1413,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
@@ -1476,6 +1488,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
|
||||
},
|
||||
"visual-engineering": {
|
||||
"model": "google/gemini-3-pro",
|
||||
"variant": "high",
|
||||
},
|
||||
"writing": {
|
||||
"model": "google/gemini-3-flash",
|
||||
|
||||
@@ -52,7 +52,7 @@ export function handleBackgroundEvent(args: {
|
||||
|
||||
const props = event.properties
|
||||
|
||||
if (event.type === "message.part.updated") {
|
||||
if (event.type === "message.part.updated" || event.type === "message.part.delta") {
|
||||
if (!props || !isRecord(props)) return
|
||||
const sessionID = getString(props, "sessionID")
|
||||
if (!sessionID) return
|
||||
|
||||
@@ -3413,4 +3413,44 @@ describe("BackgroundManager.handleEvent - non-tool event lastUpdate", () => {
|
||||
//#then - task should still be running (text event refreshed lastUpdate)
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
|
||||
test("should refresh lastUpdate on message.part.delta events (OpenCode >=1.2.0)", async () => {
|
||||
//#given - a running task with stale lastUpdate
|
||||
const client = {
|
||||
session: {
|
||||
prompt: async () => ({}),
|
||||
promptAsync: async () => ({}),
|
||||
abort: async () => ({}),
|
||||
},
|
||||
}
|
||||
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
|
||||
stubNotifyParentSession(manager)
|
||||
|
||||
const task: BackgroundTask = {
|
||||
id: "task-delta-1",
|
||||
sessionID: "session-delta-1",
|
||||
parentSessionID: "parent-1",
|
||||
parentMessageID: "msg-1",
|
||||
description: "Reasoning task with delta events",
|
||||
prompt: "Extended thinking",
|
||||
agent: "oracle",
|
||||
status: "running",
|
||||
startedAt: new Date(Date.now() - 600_000),
|
||||
progress: {
|
||||
toolCalls: 0,
|
||||
lastUpdate: new Date(Date.now() - 300_000),
|
||||
},
|
||||
}
|
||||
getTaskMap(manager).set(task.id, task)
|
||||
|
||||
//#when - a message.part.delta event arrives (reasoning-delta or text-delta in OpenCode >=1.2.0)
|
||||
manager.handleEvent({
|
||||
type: "message.part.delta",
|
||||
properties: { sessionID: "session-delta-1", field: "text", delta: "thinking..." },
|
||||
})
|
||||
await manager["checkAndInterruptStaleTasks"]()
|
||||
|
||||
//#then - task should still be running (delta event refreshed lastUpdate)
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -660,7 +660,7 @@ export class BackgroundManager {
|
||||
handleEvent(event: Event): void {
|
||||
const props = event.properties
|
||||
|
||||
if (event.type === "message.part.updated") {
|
||||
if (event.type === "message.part.updated" || event.type === "message.part.delta") {
|
||||
if (!props || typeof props !== "object" || !("sessionID" in props)) return
|
||||
const partInfo = props as unknown as MessagePartInfo
|
||||
const sessionID = partInfo?.sessionID
|
||||
@@ -1452,7 +1452,8 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
const sessionID = task.sessionID
|
||||
if (!startedAt || !sessionID) continue
|
||||
|
||||
const sessionIsRunning = allStatuses[sessionID]?.type === "running"
|
||||
const sessionStatus = allStatuses[sessionID]?.type
|
||||
const sessionIsRunning = sessionStatus !== undefined && sessionStatus !== "idle"
|
||||
const runtime = now - startedAt.getTime()
|
||||
|
||||
if (!task.progress?.lastUpdate) {
|
||||
|
||||
@@ -146,14 +146,59 @@ describe("checkAndInterruptStaleTasks", () => {
|
||||
},
|
||||
})
|
||||
|
||||
//#when — session status is "running"
|
||||
//#when — session status is "busy" (OpenCode's actual status for active LLM processing)
|
||||
await checkAndInterruptStaleTasks({
|
||||
tasks: [task],
|
||||
client: mockClient as never,
|
||||
config: { staleTimeoutMs: 180_000 },
|
||||
concurrencyManager: mockConcurrencyManager as never,
|
||||
notifyParentSession: mockNotify,
|
||||
sessionStatuses: { "ses-1": { type: "running" } },
|
||||
sessionStatuses: { "ses-1": { type: "busy" } },
|
||||
})
|
||||
|
||||
//#then — task should survive because session is actively busy
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
|
||||
it("should NOT interrupt busy session task even with very old lastUpdate", async () => {
|
||||
//#given — lastUpdate is 15min old, but session is still busy
|
||||
const task = createRunningTask({
|
||||
startedAt: new Date(Date.now() - 900_000),
|
||||
progress: {
|
||||
toolCalls: 2,
|
||||
lastUpdate: new Date(Date.now() - 900_000),
|
||||
},
|
||||
})
|
||||
|
||||
//#when — session busy, lastUpdate far exceeds any timeout
|
||||
await checkAndInterruptStaleTasks({
|
||||
tasks: [task],
|
||||
client: mockClient as never,
|
||||
config: { staleTimeoutMs: 180_000, messageStalenessTimeoutMs: 600_000 },
|
||||
concurrencyManager: mockConcurrencyManager as never,
|
||||
notifyParentSession: mockNotify,
|
||||
sessionStatuses: { "ses-1": { type: "busy" } },
|
||||
})
|
||||
|
||||
//#then — busy sessions are NEVER stale-killed (babysitter + TTL prune handle these)
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
|
||||
it("should NOT interrupt busy session even with no progress (undefined lastUpdate)", async () => {
|
||||
//#given — task has no progress at all, but session is busy
|
||||
const task = createRunningTask({
|
||||
startedAt: new Date(Date.now() - 15 * 60 * 1000),
|
||||
progress: undefined,
|
||||
})
|
||||
|
||||
//#when — session is busy
|
||||
await checkAndInterruptStaleTasks({
|
||||
tasks: [task],
|
||||
client: mockClient as never,
|
||||
config: { messageStalenessTimeoutMs: 600_000 },
|
||||
concurrencyManager: mockConcurrencyManager as never,
|
||||
notifyParentSession: mockNotify,
|
||||
sessionStatuses: { "ses-1": { type: "busy" } },
|
||||
})
|
||||
|
||||
//#then — task should survive because session is actively running
|
||||
@@ -255,6 +300,75 @@ describe("checkAndInterruptStaleTasks", () => {
|
||||
expect(task.error).toContain("Stale timeout")
|
||||
})
|
||||
|
||||
it("should NOT interrupt task when session is busy (OpenCode status), even if lastUpdate exceeds stale timeout", async () => {
|
||||
//#given — lastUpdate is 5min old but session is "busy" (OpenCode's actual status for active sessions)
|
||||
const task = createRunningTask({
|
||||
startedAt: new Date(Date.now() - 300_000),
|
||||
progress: {
|
||||
toolCalls: 2,
|
||||
lastUpdate: new Date(Date.now() - 300_000),
|
||||
},
|
||||
})
|
||||
|
||||
//#when — session status is "busy" (not "running" — OpenCode uses "busy" for active LLM processing)
|
||||
await checkAndInterruptStaleTasks({
|
||||
tasks: [task],
|
||||
client: mockClient as never,
|
||||
config: { staleTimeoutMs: 180_000 },
|
||||
concurrencyManager: mockConcurrencyManager as never,
|
||||
notifyParentSession: mockNotify,
|
||||
sessionStatuses: { "ses-1": { type: "busy" } },
|
||||
})
|
||||
|
||||
//#then — "busy" sessions must be protected from stale-kill
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
|
||||
it("should NOT interrupt task when session is in retry state", async () => {
|
||||
//#given — lastUpdate is 5min old but session is retrying
|
||||
const task = createRunningTask({
|
||||
startedAt: new Date(Date.now() - 300_000),
|
||||
progress: {
|
||||
toolCalls: 1,
|
||||
lastUpdate: new Date(Date.now() - 300_000),
|
||||
},
|
||||
})
|
||||
|
||||
//#when — session status is "retry" (OpenCode retries on transient API errors)
|
||||
await checkAndInterruptStaleTasks({
|
||||
tasks: [task],
|
||||
client: mockClient as never,
|
||||
config: { staleTimeoutMs: 180_000 },
|
||||
concurrencyManager: mockConcurrencyManager as never,
|
||||
notifyParentSession: mockNotify,
|
||||
sessionStatuses: { "ses-1": { type: "retry" } },
|
||||
})
|
||||
|
||||
//#then — retry sessions must be protected from stale-kill
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
|
||||
it("should NOT interrupt busy session even with no progress (undefined lastUpdate)", async () => {
|
||||
//#given — no progress at all, session is "busy" (thinking model with no streamed tokens yet)
|
||||
const task = createRunningTask({
|
||||
startedAt: new Date(Date.now() - 15 * 60 * 1000),
|
||||
progress: undefined,
|
||||
})
|
||||
|
||||
//#when — session is busy
|
||||
await checkAndInterruptStaleTasks({
|
||||
tasks: [task],
|
||||
client: mockClient as never,
|
||||
config: { messageStalenessTimeoutMs: 600_000 },
|
||||
concurrencyManager: mockConcurrencyManager as never,
|
||||
notifyParentSession: mockNotify,
|
||||
sessionStatuses: { "ses-1": { type: "busy" } },
|
||||
})
|
||||
|
||||
//#then — busy sessions with no progress must survive
|
||||
expect(task.status).toBe("running")
|
||||
})
|
||||
|
||||
it("should release concurrency key when interrupting a never-updated task", async () => {
|
||||
//#given
|
||||
const releaseMock = mock(() => {})
|
||||
|
||||
@@ -80,7 +80,8 @@ export async function checkAndInterruptStaleTasks(args: {
|
||||
const sessionID = task.sessionID
|
||||
if (!startedAt || !sessionID) continue
|
||||
|
||||
const sessionIsRunning = sessionStatuses?.[sessionID]?.type === "running"
|
||||
const sessionStatus = sessionStatuses?.[sessionID]?.type
|
||||
const sessionIsRunning = sessionStatus !== undefined && sessionStatus !== "idle"
|
||||
const runtime = now - startedAt.getTime()
|
||||
|
||||
if (!task.progress?.lastUpdate) {
|
||||
|
||||
@@ -241,19 +241,32 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
|
||||
expect(primary.providers[0]).toBe("openai")
|
||||
})
|
||||
|
||||
test("visual-engineering has valid fallbackChain with gemini-3-pro as primary", () => {
|
||||
test("visual-engineering has valid fallbackChain with gemini-3-pro high as primary", () => {
|
||||
// given - visual-engineering category requirement
|
||||
const visualEngineering = CATEGORY_MODEL_REQUIREMENTS["visual-engineering"]
|
||||
|
||||
// when - accessing visual-engineering requirement
|
||||
// then - fallbackChain exists with gemini-3-pro as first entry
|
||||
// then - fallbackChain: gemini-3-pro(high) → glm-5 → opus-4-6(max) → k2p5
|
||||
expect(visualEngineering).toBeDefined()
|
||||
expect(visualEngineering.fallbackChain).toBeArray()
|
||||
expect(visualEngineering.fallbackChain.length).toBeGreaterThan(0)
|
||||
expect(visualEngineering.fallbackChain).toHaveLength(4)
|
||||
|
||||
const primary = visualEngineering.fallbackChain[0]
|
||||
expect(primary.providers[0]).toBe("google")
|
||||
expect(primary.model).toBe("gemini-3-pro")
|
||||
expect(primary.variant).toBe("high")
|
||||
|
||||
const second = visualEngineering.fallbackChain[1]
|
||||
expect(second.providers[0]).toBe("zai-coding-plan")
|
||||
expect(second.model).toBe("glm-5")
|
||||
|
||||
const third = visualEngineering.fallbackChain[2]
|
||||
expect(third.model).toBe("claude-opus-4-6")
|
||||
expect(third.variant).toBe("max")
|
||||
|
||||
const fourth = visualEngineering.fallbackChain[3]
|
||||
expect(fourth.providers[0]).toBe("kimi-for-coding")
|
||||
expect(fourth.model).toBe("k2p5")
|
||||
})
|
||||
|
||||
test("quick has valid fallbackChain with claude-haiku-4-5 as primary", () => {
|
||||
@@ -318,19 +331,23 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
|
||||
expect(primary.providers[0]).toBe("google")
|
||||
})
|
||||
|
||||
test("writing has valid fallbackChain with gemini-3-flash as primary", () => {
|
||||
test("writing has valid fallbackChain with k2p5 as primary (kimi-for-coding)", () => {
|
||||
// given - writing category requirement
|
||||
const writing = CATEGORY_MODEL_REQUIREMENTS["writing"]
|
||||
|
||||
// when - accessing writing requirement
|
||||
// then - fallbackChain exists with gemini-3-flash as first entry
|
||||
// then - fallbackChain: k2p5 → gemini-3-flash → claude-sonnet-4-5
|
||||
expect(writing).toBeDefined()
|
||||
expect(writing.fallbackChain).toBeArray()
|
||||
expect(writing.fallbackChain.length).toBeGreaterThan(0)
|
||||
expect(writing.fallbackChain).toHaveLength(3)
|
||||
|
||||
const primary = writing.fallbackChain[0]
|
||||
expect(primary.model).toBe("gemini-3-flash")
|
||||
expect(primary.providers[0]).toBe("google")
|
||||
expect(primary.model).toBe("k2p5")
|
||||
expect(primary.providers[0]).toBe("kimi-for-coding")
|
||||
|
||||
const second = writing.fallbackChain[1]
|
||||
expect(second.model).toBe("gemini-3-flash")
|
||||
expect(second.providers[0]).toBe("google")
|
||||
})
|
||||
|
||||
test("all 8 categories have valid fallbackChain arrays", () => {
|
||||
|
||||
@@ -100,9 +100,10 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
"visual-engineering": {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["zai-coding-plan"], model: "glm-5" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
],
|
||||
},
|
||||
ultrabrain: {
|
||||
@@ -151,10 +152,9 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
writing: {
|
||||
fallbackChain: [
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
||||
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -162,6 +162,16 @@ Approach:
|
||||
- Draft with care
|
||||
- Polish for clarity and impact
|
||||
- Documentation, READMEs, articles, technical writing
|
||||
|
||||
ANTI-AI-SLOP RULES (NON-NEGOTIABLE):
|
||||
- NEVER use em dashes (—) or en dashes (–). Use commas, periods, ellipses, or line breaks instead. Zero tolerance.
|
||||
- Remove AI-sounding phrases: "delve", "it's important to note", "I'd be happy to", "certainly", "please don't hesitate", "leverage", "utilize", "in order to", "moving forward", "circle back", "at the end of the day", "robust", "streamline", "facilitate"
|
||||
- Pick plain words. "Use" not "utilize". "Start" not "commence". "Help" not "facilitate".
|
||||
- Use contractions naturally: "don't" not "do not", "it's" not "it is".
|
||||
- Vary sentence length. Don't make every sentence the same length.
|
||||
- NEVER start consecutive sentences with the same word.
|
||||
- No filler openings: skip "In today's world...", "As we all know...", "It goes without saying..."
|
||||
- Write like a human, not a corporate template.
|
||||
</Category_Context>`
|
||||
|
||||
export const DEEP_CATEGORY_PROMPT_APPEND = `<Category_Context>
|
||||
@@ -198,14 +208,14 @@ You are NOT an interactive assistant. You are an autonomous problem-solver.
|
||||
|
||||
|
||||
export const DEFAULT_CATEGORIES: Record<string, CategoryConfig> = {
|
||||
"visual-engineering": { model: "google/gemini-3-pro" },
|
||||
"visual-engineering": { model: "google/gemini-3-pro", variant: "high" },
|
||||
ultrabrain: { model: "openai/gpt-5.3-codex", variant: "xhigh" },
|
||||
deep: { model: "openai/gpt-5.3-codex", variant: "medium" },
|
||||
artistry: { model: "google/gemini-3-pro", variant: "high" },
|
||||
quick: { model: "anthropic/claude-haiku-4-5" },
|
||||
"unspecified-low": { model: "anthropic/claude-sonnet-4-5" },
|
||||
"unspecified-high": { model: "anthropic/claude-opus-4-6", variant: "max" },
|
||||
writing: { model: "google/gemini-3-flash" },
|
||||
writing: { model: "kimi-for-coding/k2p5" },
|
||||
}
|
||||
|
||||
export const CATEGORY_PROMPT_APPENDS: Record<string, string> = {
|
||||
|
||||
@@ -67,13 +67,14 @@ describe("sisyphus-task", () => {
|
||||
})
|
||||
|
||||
describe("DEFAULT_CATEGORIES", () => {
|
||||
test("visual-engineering category has model config", () => {
|
||||
test("visual-engineering category has model and variant config", () => {
|
||||
// given
|
||||
const category = DEFAULT_CATEGORIES["visual-engineering"]
|
||||
|
||||
// when / #then
|
||||
expect(category).toBeDefined()
|
||||
expect(category.model).toBe("google/gemini-3-pro")
|
||||
expect(category.variant).toBe("high")
|
||||
})
|
||||
|
||||
test("ultrabrain category has model and variant config", () => {
|
||||
@@ -2793,7 +2794,7 @@ describe("sisyphus-task", () => {
|
||||
{
|
||||
name: "writing",
|
||||
description: "Documentation, prose, technical writing",
|
||||
model: "google/gemini-3-flash",
|
||||
model: "kimi-for-coding/k2p5",
|
||||
},
|
||||
]
|
||||
const availableSkills = [
|
||||
|
||||
Reference in New Issue
Block a user