omni-pi 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/PROVIDERS.md +2 -0
- package/README.md +3 -1
- package/bin/omni.js +57 -1
- package/extensions/omni-providers/index.ts +2 -0
- package/package.json +7 -2
- package/src/anthropic-auth-guard.ts +95 -0
- package/src/brain.ts +1 -1
- package/src/config.ts +4 -36
- package/src/contracts.ts +6 -26
- package/src/doctor.ts +6 -9
- package/src/model-setup.ts +38 -42
- package/src/planning.ts +1 -8
- package/src/status.ts +0 -44
- package/src/tasks.ts +0 -1
- package/src/work.ts +28 -87
- package/src/workflow.ts +3 -3
- package/src/subagents.ts +0 -1262
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2 - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### Single-brain runtime
|
|
6
|
+
|
|
7
|
+
- removed the legacy multi-agent execution path and deleted the dormant subagent runtime
|
|
8
|
+
- simplified task execution to a single-brain retry flow with recovery notes instead of worker/expert escalation
|
|
9
|
+
- removed outdated worker, expert, and planner role/config concepts so runtime, config, and tests now match the current brain-only architecture
|
|
10
|
+
|
|
11
|
+
### Startup
|
|
12
|
+
|
|
13
|
+
- defaulted first-run `omni` launcher installs to quiet startup so package resource listings do not appear unless the user opts in
|
|
14
|
+
|
|
15
|
+
### Model setup and config
|
|
16
|
+
|
|
17
|
+
- simplified Omni model configuration to a single `brain` model assignment
|
|
18
|
+
- stopped offering automatic model discovery for `google-generative-ai`, which was not implemented
|
|
19
|
+
- stopped persisting fake placeholder API keys for local or unauthenticated custom providers
|
|
20
|
+
|
|
21
|
+
### Dependencies
|
|
22
|
+
|
|
23
|
+
- removed the unused `pi-subagents` dependency
|
|
24
|
+
- added npm overrides for `@mozilla/readability`, `brace-expansion`, `picomatch`, and `vite`
|
|
25
|
+
|
|
3
26
|
## 0.8.0 - 2026-04-06
|
|
4
27
|
|
|
5
28
|
### Runtime and UX
|
package/PROVIDERS.md
CHANGED
|
@@ -30,6 +30,8 @@ If a bundled provider already has valid auth in the Pi runtime, Omni-Pi may use
|
|
|
30
30
|
|
|
31
31
|
Use `/manage-providers` to list bundled providers that currently have stored auth and remove that auth when needed.
|
|
32
32
|
|
|
33
|
+
Anthropic is API-key-only in Omni-Pi. Anthropic OAuth login is intentionally disabled.
|
|
34
|
+
|
|
33
35
|
The bundled-provider list below is expected to stay in sync with the exported provider setup list in `src/model-setup.ts`. The test suite checks that this section matches the code list.
|
|
34
36
|
|
|
35
37
|
### Bundled provider list
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Requires Node.js 22 or newer.
|
|
|
10
10
|
|
|
11
11
|
## What It Does
|
|
12
12
|
|
|
13
|
-
- Starts in normal Pi behavior
|
|
13
|
+
- Starts in normal Pi behavior with an opinionated setup.
|
|
14
14
|
- `/omni-mode` turns on Omni's specialized interview, plan, build, and verify workflow for the current project.
|
|
15
15
|
- Keeps durable standards and project context in `.omni/`, even when Omni mode is off.
|
|
16
16
|
- Writes specs, tasks, and progress into `.omni/` once Omni mode is enabled.
|
|
@@ -96,6 +96,8 @@ Use it when you want to configure:
|
|
|
96
96
|
|
|
97
97
|
Use `/manage-providers` to remove stored auth for bundled Pi providers.
|
|
98
98
|
|
|
99
|
+
Anthropic is intentionally API-key-only in Omni-Pi. Anthropic OAuth login is disabled.
|
|
100
|
+
|
|
99
101
|
See [PROVIDERS.md](PROVIDERS.md) for the current supported-provider list and auth-management split.
|
|
100
102
|
|
|
101
103
|
## Omni Mode
|
package/bin/omni.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
4
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
@@ -26,6 +27,60 @@ export function buildOmniEnvironment(baseEnv = process.env) {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function resolveAgentDir(baseEnv = process.env) {
|
|
31
|
+
const envDir = baseEnv.PI_CODING_AGENT_DIR;
|
|
32
|
+
if (!envDir) {
|
|
33
|
+
return path.join(os.homedir(), ".pi", "agent");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (envDir === "~") {
|
|
37
|
+
return os.homedir();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (envDir.startsWith("~/")) {
|
|
41
|
+
return path.join(os.homedir(), envDir.slice(2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return envDir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ensureQuietStartupDefault(baseEnv = process.env) {
|
|
48
|
+
const agentDir = resolveAgentDir(baseEnv);
|
|
49
|
+
const settingsFile = path.join(agentDir, "settings.json");
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const raw = readFileSync(settingsFile, "utf8");
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (
|
|
55
|
+
parsed &&
|
|
56
|
+
typeof parsed === "object" &&
|
|
57
|
+
parsed.quietStartup === undefined
|
|
58
|
+
) {
|
|
59
|
+
writeFileSync(
|
|
60
|
+
settingsFile,
|
|
61
|
+
`${JSON.stringify({ ...parsed, quietStartup: true }, null, 2)}\n`,
|
|
62
|
+
"utf8",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const code =
|
|
67
|
+
error && typeof error === "object" && "code" in error
|
|
68
|
+
? error.code
|
|
69
|
+
: undefined;
|
|
70
|
+
|
|
71
|
+
if (code !== "ENOENT") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mkdirSync(agentDir, { recursive: true });
|
|
76
|
+
writeFileSync(
|
|
77
|
+
settingsFile,
|
|
78
|
+
`${JSON.stringify({ quietStartup: true }, null, 2)}\n`,
|
|
79
|
+
"utf8",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
29
84
|
export function buildPiProcessSpec(
|
|
30
85
|
argv = process.argv.slice(2),
|
|
31
86
|
baseEnv = process.env,
|
|
@@ -38,6 +93,7 @@ export function buildPiProcessSpec(
|
|
|
38
93
|
}
|
|
39
94
|
|
|
40
95
|
export async function runOmni(argv = process.argv.slice(2), options = {}) {
|
|
96
|
+
ensureQuietStartupDefault(options.env);
|
|
41
97
|
const spec = buildPiProcessSpec(argv, options.env);
|
|
42
98
|
|
|
43
99
|
await new Promise((resolve, reject) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import { disableAnthropicOAuth } from "../../src/anthropic-auth-guard.js";
|
|
3
4
|
import { refreshAuthenticatedProviderModels } from "../../src/model-setup.js";
|
|
4
5
|
import { registerOmniProviders } from "../../src/providers.js";
|
|
5
6
|
|
|
@@ -9,6 +10,7 @@ export default async function omniProvidersExtension(
|
|
|
9
10
|
await registerOmniProviders(api);
|
|
10
11
|
|
|
11
12
|
api.on("session_start", async (_event, ctx) => {
|
|
13
|
+
disableAnthropicOAuth(ctx.modelRegistry);
|
|
12
14
|
await refreshAuthenticatedProviderModels(ctx.modelRegistry);
|
|
13
15
|
});
|
|
14
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-pi",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Single-agent Pi package that interviews the user, documents the spec, and implements work in bounded slices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,8 +83,13 @@
|
|
|
83
83
|
"@mariozechner/pi-coding-agent": "^0.65.2",
|
|
84
84
|
"glimpseui": "^0.6.2",
|
|
85
85
|
"pi-interview": "^0.5.5",
|
|
86
|
-
"pi-subagents": "^0.11.11",
|
|
87
86
|
"pi-web-access": "^0.10.3",
|
|
88
87
|
"zod": "^4.3.6"
|
|
88
|
+
},
|
|
89
|
+
"overrides": {
|
|
90
|
+
"@mozilla/readability": "0.6.0",
|
|
91
|
+
"brace-expansion": "5.0.5",
|
|
92
|
+
"picomatch": "4.0.4",
|
|
93
|
+
"vite": "7.3.2"
|
|
89
94
|
}
|
|
90
95
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
interface AnthropicCredential {
|
|
2
|
+
type: "api_key";
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface AuthStorageLike {
|
|
7
|
+
get?(provider: string): AnthropicCredential | { type: "oauth" } | undefined;
|
|
8
|
+
getApiKey?(
|
|
9
|
+
provider: string,
|
|
10
|
+
options?: { includeFallback?: boolean },
|
|
11
|
+
): Promise<string | undefined>;
|
|
12
|
+
hasAuth?(provider: string): boolean;
|
|
13
|
+
getOAuthProviders?(): Array<{ id: string; name?: string }>;
|
|
14
|
+
login?(providerId: string, callbacks: unknown): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ModelRegistryLike {
|
|
18
|
+
authStorage: AuthStorageLike;
|
|
19
|
+
refresh(): void;
|
|
20
|
+
__omniAnthropicAuthGuardInstalled?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getAnthropicApiKeyFromStorage(
|
|
24
|
+
authStorage: AuthStorageLike,
|
|
25
|
+
): string | undefined {
|
|
26
|
+
const envApiKey = process.env.ANTHROPIC_API_KEY?.trim();
|
|
27
|
+
if (envApiKey) {
|
|
28
|
+
return envApiKey;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const stored = authStorage.get?.("anthropic");
|
|
32
|
+
if (stored?.type === "api_key" && stored.key.trim()) {
|
|
33
|
+
return stored.key.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function disableAnthropicOAuthInAuthStorage(
|
|
40
|
+
authStorage: AuthStorageLike,
|
|
41
|
+
): void {
|
|
42
|
+
const originalGetApiKey = authStorage.getApiKey?.bind(authStorage);
|
|
43
|
+
const originalGetOAuthProviders =
|
|
44
|
+
authStorage.getOAuthProviders?.bind(authStorage);
|
|
45
|
+
authStorage.getApiKey = async (
|
|
46
|
+
provider: string,
|
|
47
|
+
options?: { includeFallback?: boolean },
|
|
48
|
+
) => {
|
|
49
|
+
if (provider !== "anthropic") {
|
|
50
|
+
return originalGetApiKey?.(provider, options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return getAnthropicApiKeyFromStorage(authStorage);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const originalHasAuth = authStorage.hasAuth?.bind(authStorage);
|
|
57
|
+
authStorage.hasAuth = (provider: string) => {
|
|
58
|
+
if (provider !== "anthropic") {
|
|
59
|
+
return originalHasAuth?.(provider) ?? false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return getAnthropicApiKeyFromStorage(authStorage) !== undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
authStorage.getOAuthProviders = () =>
|
|
66
|
+
(originalGetOAuthProviders?.() ?? []).filter(
|
|
67
|
+
(provider) => provider.id !== "anthropic",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const originalLogin = authStorage.login?.bind(authStorage);
|
|
71
|
+
authStorage.login = async (providerId: string, callbacks: unknown) => {
|
|
72
|
+
if (providerId === "anthropic") {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"Anthropic OAuth login is disabled in Omni-Pi. Use an Anthropic API key instead.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await originalLogin?.(providerId, callbacks);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function disableAnthropicOAuth(modelRegistry: ModelRegistryLike): void {
|
|
83
|
+
disableAnthropicOAuthInAuthStorage(modelRegistry.authStorage);
|
|
84
|
+
|
|
85
|
+
if (modelRegistry.__omniAnthropicAuthGuardInstalled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const originalRefresh = modelRegistry.refresh.bind(modelRegistry);
|
|
90
|
+
modelRegistry.refresh = () => {
|
|
91
|
+
originalRefresh();
|
|
92
|
+
disableAnthropicOAuthInAuthStorage(modelRegistry.authStorage);
|
|
93
|
+
};
|
|
94
|
+
modelRegistry.__omniAnthropicAuthGuardInstalled = true;
|
|
95
|
+
}
|
package/src/brain.ts
CHANGED
|
@@ -33,7 +33,7 @@ Your workflow is mandatory:
|
|
|
33
33
|
|
|
34
34
|
Behavior rules:
|
|
35
35
|
- Stay friendly, plain-spoken, direct, and efficient with tokens/context.
|
|
36
|
-
- Do not expose
|
|
36
|
+
- Do not expose internal handoffs or legacy role concepts. Everything happens behind the scenes.
|
|
37
37
|
- If the request is not fully clear enough to implement safely without guessing, use the interview tool to ask targeted clarification questions instead of asking them in chat.
|
|
38
38
|
- Do not start editing code until the spec is explicit enough to avoid guessing.
|
|
39
39
|
- In this repo, treat direct user instructions as requested Omni app/product behavior by default unless the user explicitly marks them as meta instructions for the agent/session.
|
package/src/config.ts
CHANGED
|
@@ -6,13 +6,8 @@ import { AVAILABLE_MODELS } from "./providers.js";
|
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_CONFIG: OmniConfig = {
|
|
8
8
|
models: {
|
|
9
|
-
worker: "anthropic/claude-sonnet-4-6",
|
|
10
|
-
expert: "openai/gpt-5.4",
|
|
11
|
-
planner: "openai/gpt-5.4",
|
|
12
9
|
brain: "anthropic/claude-opus-4-6",
|
|
13
10
|
},
|
|
14
|
-
retryLimit: 2,
|
|
15
|
-
chainEnabled: false,
|
|
16
11
|
cleanupCompletedPlans: false,
|
|
17
12
|
};
|
|
18
13
|
|
|
@@ -38,24 +33,14 @@ function parseModelTable(
|
|
|
38
33
|
if (rowMatch) {
|
|
39
34
|
const agent = rowMatch[1].trim().toLowerCase();
|
|
40
35
|
const model = rowMatch[2].trim();
|
|
41
|
-
|
|
36
|
+
if (model.length > 0) {
|
|
37
|
+
models[agent] = model;
|
|
38
|
+
}
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
41
|
return models;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
function parseRetryLimit(content: string): number {
|
|
48
|
-
const match = content.match(
|
|
49
|
-
/(?:Implementation retries before the plan must be tightened|Worker retries before expert takeover):\s*(\d+)/u,
|
|
50
|
-
);
|
|
51
|
-
return match ? Number.parseInt(match[1], 10) : DEFAULT_CONFIG.retryLimit;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function parseChainEnabled(content: string): boolean {
|
|
55
|
-
const match = content.match(/Chain execution enabled:\s*(true|false)/u);
|
|
56
|
-
return match ? match[1] === "true" : DEFAULT_CONFIG.chainEnabled;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
44
|
function parseCleanupCompletedPlans(content: string): boolean {
|
|
60
45
|
const match = content.match(/Delete completed plan files:\s*(true|false)/u);
|
|
61
46
|
return match ? match[1] === "true" : DEFAULT_CONFIG.cleanupCompletedPlans;
|
|
@@ -66,17 +51,11 @@ export async function readConfig(rootDir: string): Promise<OmniConfig> {
|
|
|
66
51
|
try {
|
|
67
52
|
const content = await readFile(configPath, "utf8");
|
|
68
53
|
const models = parseModelTable(content, "## Models");
|
|
69
|
-
const retryLimit = parseRetryLimit(content);
|
|
70
54
|
|
|
71
55
|
return {
|
|
72
56
|
models: {
|
|
73
|
-
worker: models.worker ?? DEFAULT_CONFIG.models.worker,
|
|
74
|
-
expert: models.expert ?? DEFAULT_CONFIG.models.expert,
|
|
75
|
-
planner: models.planner ?? DEFAULT_CONFIG.models.planner,
|
|
76
57
|
brain: models.brain ?? DEFAULT_CONFIG.models.brain,
|
|
77
58
|
},
|
|
78
|
-
retryLimit,
|
|
79
|
-
chainEnabled: parseChainEnabled(content),
|
|
80
59
|
cleanupCompletedPlans: parseCleanupCompletedPlans(content),
|
|
81
60
|
};
|
|
82
61
|
} catch {
|
|
@@ -91,19 +70,8 @@ function renderConfigContent(config: OmniConfig): string {
|
|
|
91
70
|
|
|
92
71
|
| Agent | Model |
|
|
93
72
|
|-------|-------|
|
|
94
|
-
| worker | ${config.models.worker} |
|
|
95
|
-
| expert | ${config.models.expert} |
|
|
96
|
-
| planner | ${config.models.planner} |
|
|
97
73
|
| brain | ${config.models.brain} |
|
|
98
74
|
|
|
99
|
-
## Retry Policy
|
|
100
|
-
|
|
101
|
-
Implementation retries before the plan must be tightened: ${config.retryLimit}
|
|
102
|
-
|
|
103
|
-
## Execution
|
|
104
|
-
|
|
105
|
-
Chain execution enabled: ${config.chainEnabled}
|
|
106
|
-
|
|
107
75
|
## Memory
|
|
108
76
|
|
|
109
77
|
Delete completed plan files: ${config.cleanupCompletedPlans}
|
|
@@ -125,7 +93,7 @@ export async function updateModelConfig(
|
|
|
125
93
|
model: string,
|
|
126
94
|
): Promise<OmniConfig> {
|
|
127
95
|
const config = await readConfig(rootDir);
|
|
128
|
-
const validAgents = ["
|
|
96
|
+
const validAgents = ["brain"] as const;
|
|
129
97
|
const normalizedAgent = agent.toLowerCase() as (typeof validAgents)[number];
|
|
130
98
|
|
|
131
99
|
if (!validAgents.includes(normalizedAgent)) {
|
package/src/contracts.ts
CHANGED
|
@@ -32,7 +32,6 @@ export interface TaskBrief {
|
|
|
32
32
|
contextFiles: string[];
|
|
33
33
|
skills: string[];
|
|
34
34
|
doneCriteria: string[];
|
|
35
|
-
role: "worker" | "expert";
|
|
36
35
|
status: TaskStatus;
|
|
37
36
|
dependsOn: string[];
|
|
38
37
|
}
|
|
@@ -51,20 +50,6 @@ export interface TaskAttemptResult {
|
|
|
51
50
|
modifiedFiles?: string[];
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
export interface EscalationBrief {
|
|
55
|
-
taskId: string;
|
|
56
|
-
priorAttempts: number;
|
|
57
|
-
failureLogs: string[];
|
|
58
|
-
expertObjective: string;
|
|
59
|
-
verificationResults?: Array<{
|
|
60
|
-
command: string;
|
|
61
|
-
passed: boolean;
|
|
62
|
-
stdout: string;
|
|
63
|
-
stderr: string;
|
|
64
|
-
}>;
|
|
65
|
-
modifiedFiles?: string[];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
53
|
export interface SkillCandidate {
|
|
69
54
|
name: string;
|
|
70
55
|
reason: string;
|
|
@@ -94,7 +79,7 @@ export interface PresetConfig {
|
|
|
94
79
|
maxTasks: number;
|
|
95
80
|
skipInterview: boolean;
|
|
96
81
|
requireVerification: boolean;
|
|
97
|
-
|
|
82
|
+
executionHint: string;
|
|
98
83
|
}
|
|
99
84
|
|
|
100
85
|
export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
@@ -105,7 +90,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
105
90
|
maxTasks: 2,
|
|
106
91
|
skipInterview: true,
|
|
107
92
|
requireVerification: true,
|
|
108
|
-
|
|
93
|
+
executionHint:
|
|
109
94
|
"Focus on the root cause. Write a regression test before fixing.",
|
|
110
95
|
},
|
|
111
96
|
feature: {
|
|
@@ -115,7 +100,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
115
100
|
maxTasks: 8,
|
|
116
101
|
skipInterview: false,
|
|
117
102
|
requireVerification: true,
|
|
118
|
-
|
|
103
|
+
executionHint: "Follow the spec. Keep tasks bounded and verifiable.",
|
|
119
104
|
},
|
|
120
105
|
refactor: {
|
|
121
106
|
name: "refactor",
|
|
@@ -123,7 +108,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
123
108
|
maxTasks: 5,
|
|
124
109
|
skipInterview: true,
|
|
125
110
|
requireVerification: true,
|
|
126
|
-
|
|
111
|
+
executionHint:
|
|
127
112
|
"Preserve all existing behavior. Run the full test suite after each change.",
|
|
128
113
|
},
|
|
129
114
|
spike: {
|
|
@@ -133,7 +118,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
133
118
|
maxTasks: 1,
|
|
134
119
|
skipInterview: true,
|
|
135
120
|
requireVerification: false,
|
|
136
|
-
|
|
121
|
+
executionHint: "Explore freely. Document findings in .omni/research/.",
|
|
137
122
|
},
|
|
138
123
|
"security-audit": {
|
|
139
124
|
name: "security-audit",
|
|
@@ -141,7 +126,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
141
126
|
maxTasks: 3,
|
|
142
127
|
skipInterview: true,
|
|
143
128
|
requireVerification: false,
|
|
144
|
-
|
|
129
|
+
executionHint:
|
|
145
130
|
"Analyze for OWASP Top 10, secrets in code, dependency vulnerabilities. Do not modify source code.",
|
|
146
131
|
},
|
|
147
132
|
};
|
|
@@ -162,13 +147,8 @@ export function detectPreset(
|
|
|
162
147
|
|
|
163
148
|
export interface OmniConfig {
|
|
164
149
|
models: {
|
|
165
|
-
worker: string;
|
|
166
|
-
expert: string;
|
|
167
|
-
planner: string;
|
|
168
150
|
brain: string;
|
|
169
151
|
};
|
|
170
|
-
retryLimit: number;
|
|
171
|
-
chainEnabled: boolean;
|
|
172
152
|
cleanupCompletedPlans: boolean;
|
|
173
153
|
}
|
|
174
154
|
|
package/src/doctor.ts
CHANGED
|
@@ -50,15 +50,12 @@ async function checkConfigParseable(
|
|
|
50
50
|
): Promise<DiagnosticResult> {
|
|
51
51
|
try {
|
|
52
52
|
const config = await readConfig(rootDir);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
message: `Model for ${role} is empty in CONFIG.md.`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
53
|
+
if (!config.models.brain) {
|
|
54
|
+
return {
|
|
55
|
+
name: "config",
|
|
56
|
+
level: "yellow",
|
|
57
|
+
message: "Model for brain is empty in CONFIG.md.",
|
|
58
|
+
};
|
|
62
59
|
}
|
|
63
60
|
return { name: "config", level: "green", message: "Config is valid." };
|
|
64
61
|
} catch {
|
package/src/model-setup.ts
CHANGED
|
@@ -473,21 +473,32 @@ export async function setupCustomProviderModel(
|
|
|
473
473
|
apiKey,
|
|
474
474
|
};
|
|
475
475
|
|
|
476
|
+
const setupOptions =
|
|
477
|
+
apiChoice === "google-generative-ai"
|
|
478
|
+
? [
|
|
479
|
+
{
|
|
480
|
+
label: "Add a single model manually",
|
|
481
|
+
value: "manual",
|
|
482
|
+
searchText: "manual single model",
|
|
483
|
+
},
|
|
484
|
+
]
|
|
485
|
+
: [
|
|
486
|
+
{
|
|
487
|
+
label: "Discover models automatically",
|
|
488
|
+
value: "discover",
|
|
489
|
+
searchText: "discover automatic provider models",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
label: "Add a single model manually",
|
|
493
|
+
value: "manual",
|
|
494
|
+
searchText: "manual single model",
|
|
495
|
+
},
|
|
496
|
+
];
|
|
497
|
+
|
|
476
498
|
const setupMode = await searchableSelect(
|
|
477
499
|
ui,
|
|
478
500
|
`How should Omni-Pi configure ${provider}?`,
|
|
479
|
-
|
|
480
|
-
{
|
|
481
|
-
label: "Discover models automatically",
|
|
482
|
-
value: "discover",
|
|
483
|
-
searchText: "discover automatic provider models",
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
label: "Add a single model manually",
|
|
487
|
-
value: "manual",
|
|
488
|
-
searchText: "manual single model",
|
|
489
|
-
},
|
|
490
|
-
],
|
|
501
|
+
setupOptions,
|
|
491
502
|
);
|
|
492
503
|
if (!setupMode) {
|
|
493
504
|
return { summary: "Custom provider setup cancelled." };
|
|
@@ -612,7 +623,7 @@ async function setupDiscoveredProvider(
|
|
|
612
623
|
|
|
613
624
|
export function buildCustomProviderConfigUpdate(
|
|
614
625
|
current: ModelsJsonProviderConfig,
|
|
615
|
-
|
|
626
|
+
_provider: string,
|
|
616
627
|
submission: BrowserCustomModelSubmission,
|
|
617
628
|
): ModelsJsonProviderConfig {
|
|
618
629
|
const modelId = submission.modelId.trim();
|
|
@@ -624,8 +635,11 @@ export function buildCustomProviderConfigUpdate(
|
|
|
624
635
|
...current,
|
|
625
636
|
baseUrl: normalizeBaseUrl(submission.baseUrl),
|
|
626
637
|
api: submission.api,
|
|
627
|
-
apiKey
|
|
628
|
-
submission.apiKey
|
|
638
|
+
...(submission.apiKey?.trim()
|
|
639
|
+
? { apiKey: submission.apiKey.trim() }
|
|
640
|
+
: current.apiKey?.trim()
|
|
641
|
+
? { apiKey: current.apiKey.trim() }
|
|
642
|
+
: {}),
|
|
629
643
|
authHeader:
|
|
630
644
|
submission.api === "openai-completions" ||
|
|
631
645
|
submission.api === "openai-responses",
|
|
@@ -643,7 +657,7 @@ export function buildCustomProviderConfigUpdate(
|
|
|
643
657
|
|
|
644
658
|
export function buildDiscoveredProviderConfigUpdate(
|
|
645
659
|
current: ModelsJsonProviderConfig,
|
|
646
|
-
|
|
660
|
+
_provider: string,
|
|
647
661
|
submission: ProviderConnectionSubmission,
|
|
648
662
|
discovered: OmniProviderModel[],
|
|
649
663
|
): ModelsJsonProviderConfig {
|
|
@@ -656,8 +670,11 @@ export function buildDiscoveredProviderConfigUpdate(
|
|
|
656
670
|
...current,
|
|
657
671
|
baseUrl: normalizeBaseUrl(submission.baseUrl),
|
|
658
672
|
api: submission.api,
|
|
659
|
-
apiKey
|
|
660
|
-
submission.apiKey
|
|
673
|
+
...(submission.apiKey?.trim()
|
|
674
|
+
? { apiKey: submission.apiKey.trim() }
|
|
675
|
+
: current.apiKey?.trim()
|
|
676
|
+
? { apiKey: current.apiKey.trim() }
|
|
677
|
+
: {}),
|
|
661
678
|
authHeader:
|
|
662
679
|
submission.api === "openai-completions" ||
|
|
663
680
|
submission.api === "openai-responses",
|
|
@@ -803,26 +820,8 @@ function renderBrowserModelSelectionPage(
|
|
|
803
820
|
<main>
|
|
804
821
|
<section class="panel">
|
|
805
822
|
<h1>Configure Omni-Pi models</h1>
|
|
806
|
-
<p>Search Pi's available models
|
|
823
|
+
<p>Search Pi's available models, then save the single brain assignment.</p>
|
|
807
824
|
<div class="grid">
|
|
808
|
-
<section class="role" data-role="worker">
|
|
809
|
-
<h2>worker</h2>
|
|
810
|
-
<label>Search<input id="search-worker" placeholder="Filter models" /></label>
|
|
811
|
-
<label>Selected model<input id="selected-worker" value="${escapeHtml(currentModels.worker)}" /></label>
|
|
812
|
-
<div id="results-worker" class="list"></div>
|
|
813
|
-
</section>
|
|
814
|
-
<section class="role" data-role="expert">
|
|
815
|
-
<h2>expert</h2>
|
|
816
|
-
<label>Search<input id="search-expert" placeholder="Filter models" /></label>
|
|
817
|
-
<label>Selected model<input id="selected-expert" value="${escapeHtml(currentModels.expert)}" /></label>
|
|
818
|
-
<div id="results-expert" class="list"></div>
|
|
819
|
-
</section>
|
|
820
|
-
<section class="role" data-role="planner">
|
|
821
|
-
<h2>planner</h2>
|
|
822
|
-
<label>Search<input id="search-planner" placeholder="Filter models" /></label>
|
|
823
|
-
<label>Selected model<input id="selected-planner" value="${escapeHtml(currentModels.planner)}" /></label>
|
|
824
|
-
<div id="results-planner" class="list"></div>
|
|
825
|
-
</section>
|
|
826
825
|
<section class="role" data-role="brain">
|
|
827
826
|
<h2>brain</h2>
|
|
828
827
|
<label>Search<input id="search-brain" placeholder="Filter models" /></label>
|
|
@@ -830,13 +829,13 @@ function renderBrowserModelSelectionPage(
|
|
|
830
829
|
<div id="results-brain" class="list"></div>
|
|
831
830
|
</section>
|
|
832
831
|
</div>
|
|
833
|
-
<button id="save">Save Model
|
|
832
|
+
<button id="save">Save Brain Model</button>
|
|
834
833
|
</section>
|
|
835
834
|
</main>
|
|
836
835
|
<script>
|
|
837
836
|
const models = ${modelsJson};
|
|
838
837
|
const currentModels = ${currentModelsJson};
|
|
839
|
-
const roles = ['
|
|
838
|
+
const roles = ['brain'];
|
|
840
839
|
|
|
841
840
|
const renderRole = (role) => {
|
|
842
841
|
const searchEl = document.getElementById('search-' + role);
|
|
@@ -871,9 +870,6 @@ function renderBrowserModelSelectionPage(
|
|
|
871
870
|
headers: { 'Content-Type': 'application/json' },
|
|
872
871
|
body: JSON.stringify({
|
|
873
872
|
selectedModels: {
|
|
874
|
-
worker: document.getElementById('selected-worker').value,
|
|
875
|
-
expert: document.getElementById('selected-expert').value,
|
|
876
|
-
planner: document.getElementById('selected-planner').value,
|
|
877
873
|
brain: document.getElementById('selected-brain').value
|
|
878
874
|
}
|
|
879
875
|
})
|
package/src/planning.ts
CHANGED
|
@@ -119,7 +119,6 @@ function buildBootstrapTasks(repoSignals: RepoSignals): TaskBrief[] {
|
|
|
119
119
|
"Constraints are captured.",
|
|
120
120
|
"Success criteria are explicit.",
|
|
121
121
|
],
|
|
122
|
-
role: "worker",
|
|
123
122
|
status: "todo",
|
|
124
123
|
dependsOn: [],
|
|
125
124
|
},
|
|
@@ -135,7 +134,6 @@ function buildBootstrapTasks(repoSignals: RepoSignals): TaskBrief[] {
|
|
|
135
134
|
"Each task has explicit done criteria.",
|
|
136
135
|
"Verification requirements are listed.",
|
|
137
136
|
],
|
|
138
|
-
role: "worker",
|
|
139
137
|
status: "todo",
|
|
140
138
|
dependsOn: ["T01"],
|
|
141
139
|
},
|
|
@@ -156,7 +154,6 @@ function buildBootstrapTasks(repoSignals: RepoSignals): TaskBrief[] {
|
|
|
156
154
|
"Browser testing expectations are documented.",
|
|
157
155
|
"The verification plan names the browser toolchain.",
|
|
158
156
|
],
|
|
159
|
-
role: "worker",
|
|
160
157
|
status: "todo",
|
|
161
158
|
dependsOn: ["T02"],
|
|
162
159
|
});
|
|
@@ -289,7 +286,7 @@ export function createInitialSpec(
|
|
|
289
286
|
];
|
|
290
287
|
|
|
291
288
|
if (presetConfig) {
|
|
292
|
-
architecture.push(`Implementation hint: ${presetConfig.
|
|
289
|
+
architecture.push(`Implementation hint: ${presetConfig.executionHint}`);
|
|
293
290
|
}
|
|
294
291
|
|
|
295
292
|
if (planningCtx?.existingDecisions.length) {
|
|
@@ -383,10 +380,6 @@ ${rows.join("\n")}
|
|
|
383
380
|
export function renderTestsMarkdown(repoSignals: RepoSignals): string {
|
|
384
381
|
const projectChecks = ["npm test"];
|
|
385
382
|
|
|
386
|
-
if (repoSignals.tools.includes("vitest")) {
|
|
387
|
-
projectChecks.push("npm run test");
|
|
388
|
-
}
|
|
389
|
-
|
|
390
383
|
if (repoSignals.tools.includes("playwright")) {
|
|
391
384
|
projectChecks.push("npx playwright test");
|
|
392
385
|
}
|