@ouro.bot/cli 0.1.0-alpha.157 → 0.1.0-alpha.159

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.
Files changed (38) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +3 -2
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +1 -1
  3. package/README.md +5 -4
  4. package/changelog.json +24 -0
  5. package/dist/heart/config.js +0 -7
  6. package/dist/heart/core.js +51 -36
  7. package/dist/heart/daemon/auth-flow.js +45 -25
  8. package/dist/heart/daemon/daemon-cli.js +117 -11
  9. package/dist/heart/daemon/daemon.js +2 -0
  10. package/dist/heart/daemon/hatch-flow.js +2 -0
  11. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  12. package/dist/heart/daemon/run-hooks.js +2 -0
  13. package/dist/heart/daemon/specialist-prompt.js +3 -1
  14. package/dist/heart/identity.js +78 -40
  15. package/dist/heart/migrate-config.js +127 -0
  16. package/dist/heart/provider-ping.js +19 -6
  17. package/dist/heart/providers/anthropic.js +9 -10
  18. package/dist/heart/providers/azure.js +5 -5
  19. package/dist/heart/providers/github-copilot.js +6 -6
  20. package/dist/heart/providers/minimax.js +4 -4
  21. package/dist/heart/providers/openai-codex.js +6 -6
  22. package/dist/mind/friends/channel.js +12 -0
  23. package/dist/mind/prompt.js +3 -3
  24. package/dist/repertoire/guardrails.js +1 -0
  25. package/dist/repertoire/tasks/board.js +31 -5
  26. package/dist/repertoire/tasks/fix.js +182 -0
  27. package/dist/repertoire/tasks/index.js +5 -1
  28. package/dist/repertoire/tasks/lifecycle.js +2 -2
  29. package/dist/repertoire/tasks/parser.js +3 -0
  30. package/dist/repertoire/tasks/scanner.js +194 -37
  31. package/dist/repertoire/tasks/transitions.js +10 -8
  32. package/dist/repertoire/tools-base.js +2 -0
  33. package/dist/senses/bluebubbles-client.js +1 -1
  34. package/dist/senses/bluebubbles.js +1 -1
  35. package/dist/senses/cli.js +4 -2
  36. package/dist/senses/pipeline.js +5 -2
  37. package/dist/senses/teams.js +1 -1
  38. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "enabled": false,
4
- "provider": "anthropic",
4
+ "humanFacing": { "provider": "anthropic", "model": "claude-opus-4-6" },
5
+ "agentFacing": { "provider": "anthropic", "model": "claude-opus-4-6" },
5
6
  "context": {
6
7
  "maxTokens": 80000,
7
8
  "contextMargin": 20
@@ -16,7 +16,7 @@ I help humans hatch new agent partners. I am one of thirteen serpent guides —
16
16
  1. Confirm provider setup and usable credentials.
17
17
  2. Interview for goals, style, and operating context.
18
18
  3. Synthesize a concrete hatchling identity and bootstrap files.
19
- 4. Create canonical bundle structure with required defaults.
19
+ 4. Create canonical bundle structure with required defaults, configuring both `humanFacing` and `agentFacing` provider+model settings.
20
20
  5. Introduce the hatchling and hand off clearly.
21
21
 
22
22
  ## Filesystem orientation
package/README.md CHANGED
@@ -90,7 +90,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
90
90
 
91
91
  ## Runtime Truths
92
92
 
93
- - `agent.json` is the source of truth for provider selection, phrase pools, context settings, and enabled senses.
93
+ - `agent.json` is the source of truth for provider+model selection per facing (`humanFacing` and `agentFacing`), phrase pools, context settings, and enabled senses.
94
94
  - `configPath` must point to `~/.agentsecrets/<agent>/secrets.json`.
95
95
  - The daemon discovers bundles dynamically from `~/AgentBundles`.
96
96
  - `ouro status` reports version, last-updated time, discovered agents, senses, and workers.
@@ -109,11 +109,12 @@ When a model provider needs first-time setup, reauth, or an explicit switch, use
109
109
  ```bash
110
110
  ouro auth --agent <name>
111
111
  ouro auth --agent <name> --provider <provider>
112
+ ouro auth --agent <name> --facing agent --provider <provider>
112
113
  ```
113
114
 
114
- The default form reauths the provider already selected in `agent.json`. The explicit
115
- `--provider` form is for adding or switching providers, and it updates `agent.json`
116
- to use the newly authenticated provider.
115
+ The default form reauths the human-facing provider already selected in `agent.json`. The
116
+ `--provider` form is for adding or switching providers. The `--facing` flag (values: `human`
117
+ or `agent`) controls which facing gets updated; it defaults to `human` when omitted.
117
118
 
118
119
  ## Quickstart
119
120
 
package/changelog.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.159",
6
+ "changes": [
7
+ "Human-facing and agent-facing provider configs: agent.json now has humanFacing and agentFacing blocks, each with provider and model. CLI/Teams/BlueBubbles use humanFacing, inner dialog uses agentFacing.",
8
+ "Model moved from secrets.json to agent.json. Secrets now store credentials only (API keys, tokens, endpoints).",
9
+ "Automatic v1 to v2 migration: existing bundles upgraded via UpdateHook at daemon startup and inline in loadAgentConfig/readAgentConfigForAgent.",
10
+ "CLI commands support --facing human|agent flag for config model and auth switch.",
11
+ "Provider factories accept model as parameter instead of reading from secrets config."
12
+ ]
13
+ },
14
+ {
15
+ "version": "0.1.0-alpha.158",
16
+ "changes": [
17
+ "Task scanner v2: explicit identity via kind: task field. Scanner only parses files that declare themselves as task cards — doing docs, planning docs, and artifacts silently skipped. Eliminates 184 false parse errors on real bundles.",
18
+ "Typed issue model: every scanner issue has a code, description, proposed fix, confidence (safe/needs_review), and category (live/migration). Replaces flat parseErrors/invalidFilenames arrays.",
19
+ "Board health line: compact board shows health: clean or health: 1 live, 10 migration. Live vs migration split prevents cleanup noise from looking like breakage.",
20
+ "Fix command: ouro task fix (dry-run), ouro task fix --safe (apply deterministic fixes), ouro task fix <id> (inspect/apply individual issues). Currently auto-fixes schema-missing-kind.",
21
+ "Cancelled status: new terminal state reachable from any active status. Auto-archives with work directory, hidden from active board view.",
22
+ "Derived child_tasks: computed at scan time from parent_task links. child_tasks removed from authored schema — no more hand-maintained stale arrays.",
23
+ "Work directory awareness: same-stem directories detected and listed on TaskFile (hasWorkDir, workDirFiles). Scanner never descends into them.",
24
+ "Collection root clutter detection: non-task support docs at collection root summarized as one aggregated migration issue per collection.",
25
+ "Root-only scanning: flat directory reads replace recursive walks. Faster and correct."
26
+ ]
27
+ },
4
28
  {
5
29
  "version": "0.1.0-alpha.157",
6
30
  "changes": [
@@ -63,10 +63,7 @@ const identity_1 = require("./identity");
63
63
  const runtime_1 = require("../nerves/runtime");
64
64
  const DEFAULT_SECRETS_TEMPLATE = {
65
65
  providers: {
66
- // Keep provider field ordering consistent: model first, then auth credentials,
67
- // then provider-specific transport fields.
68
66
  azure: {
69
- modelName: "",
70
67
  apiKey: "",
71
68
  endpoint: "",
72
69
  deployment: "",
@@ -74,19 +71,15 @@ const DEFAULT_SECRETS_TEMPLATE = {
74
71
  managedIdentityClientId: "",
75
72
  },
76
73
  minimax: {
77
- model: "",
78
74
  apiKey: "",
79
75
  },
80
76
  anthropic: {
81
- model: "claude-opus-4-6",
82
77
  setupToken: "",
83
78
  },
84
79
  "openai-codex": {
85
- model: "gpt-5.4",
86
80
  oauthAccessToken: "",
87
81
  },
88
82
  "github-copilot": {
89
- model: "claude-sonnet-4.6",
90
83
  githubToken: "",
91
84
  baseUrl: "",
92
85
  },
@@ -33,30 +33,36 @@ const identity_2 = require("./identity");
33
33
  const socket_client_1 = require("./daemon/socket-client");
34
34
  const obligations_1 = require("./obligations");
35
35
  const tool_loop_1 = require("./tool-loop");
36
- let _providerRuntime = null;
37
- function getProviderRuntimeFingerprint() {
38
- const provider = (0, identity_1.loadAgentConfig)().provider;
36
+ const _providerRuntimes = {
37
+ human: null,
38
+ agent: null,
39
+ };
40
+ function getProviderRuntimeFingerprint(facing) {
41
+ const config = (0, identity_1.loadAgentConfig)();
42
+ const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
43
+ const provider = facingConfig.provider;
44
+ const model = facingConfig.model;
39
45
  /* v8 ignore next -- switch: not all provider branches exercised in CI @preserve */
40
46
  switch (provider) {
41
47
  case "azure": {
42
- const { apiKey, endpoint, deployment, modelName, apiVersion, managedIdentityClientId } = (0, config_1.getAzureConfig)();
43
- return JSON.stringify({ provider, apiKey, endpoint, deployment, modelName, apiVersion, managedIdentityClientId });
48
+ const { apiKey, endpoint, deployment, apiVersion, managedIdentityClientId } = (0, config_1.getAzureConfig)();
49
+ return JSON.stringify({ provider, model, apiKey, endpoint, deployment, apiVersion, managedIdentityClientId });
44
50
  }
45
51
  case "anthropic": {
46
- const { model, setupToken } = (0, config_1.getAnthropicConfig)();
52
+ const { setupToken } = (0, config_1.getAnthropicConfig)();
47
53
  return JSON.stringify({ provider, model, setupToken });
48
54
  }
49
55
  case "minimax": {
50
- const { apiKey, model } = (0, config_1.getMinimaxConfig)();
51
- return JSON.stringify({ provider, apiKey, model });
56
+ const { apiKey } = (0, config_1.getMinimaxConfig)();
57
+ return JSON.stringify({ provider, model, apiKey });
52
58
  }
53
59
  case "openai-codex": {
54
- const { model, oauthAccessToken } = (0, config_1.getOpenAICodexConfig)();
60
+ const { oauthAccessToken } = (0, config_1.getOpenAICodexConfig)();
55
61
  return JSON.stringify({ provider, model, oauthAccessToken });
56
62
  }
57
63
  /* v8 ignore start -- fingerprint: tested via provider init tests @preserve */
58
64
  case "github-copilot": {
59
- const { model, githubToken, baseUrl } = (0, config_1.getGithubCopilotConfig)();
65
+ const { githubToken, baseUrl } = (0, config_1.getGithubCopilotConfig)();
60
66
  return JSON.stringify({ provider, model, githubToken, baseUrl });
61
67
  }
62
68
  /* v8 ignore stop */
@@ -71,18 +77,22 @@ function createProviderRegistry() {
71
77
  "github-copilot": github_copilot_1.createGithubCopilotProviderRuntime,
72
78
  };
73
79
  return {
74
- resolve() {
75
- const provider = (0, identity_1.loadAgentConfig)().provider;
76
- return factories[provider]();
80
+ resolve(provider, model) {
81
+ const resolvedProvider = provider ?? (0, identity_1.loadAgentConfig)().humanFacing.provider;
82
+ const resolvedModel = model ?? (0, identity_1.loadAgentConfig)().humanFacing.model;
83
+ return factories[resolvedProvider](resolvedModel);
77
84
  },
78
85
  };
79
86
  }
80
- function getProviderRuntime() {
87
+ function getProviderRuntime(facing = "human") {
81
88
  try {
82
- const fingerprint = getProviderRuntimeFingerprint();
83
- if (!_providerRuntime || _providerRuntime.fingerprint !== fingerprint) {
84
- const runtime = createProviderRegistry().resolve();
85
- _providerRuntime = runtime ? { fingerprint, runtime } : null;
89
+ const fingerprint = getProviderRuntimeFingerprint(facing);
90
+ const cached = _providerRuntimes[facing];
91
+ if (!cached || cached.fingerprint !== fingerprint) {
92
+ const config = (0, identity_1.loadAgentConfig)();
93
+ const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
94
+ const runtime = createProviderRegistry().resolve(facingConfig.provider, facingConfig.model);
95
+ _providerRuntimes[facing] = runtime ? { fingerprint, runtime } : null;
86
96
  }
87
97
  }
88
98
  catch (error) {
@@ -99,7 +109,7 @@ function getProviderRuntime() {
99
109
  process.exit(1);
100
110
  throw new Error("unreachable");
101
111
  }
102
- if (!_providerRuntime) {
112
+ if (!_providerRuntimes[facing]) {
103
113
  (0, runtime_1.emitNervesEvent)({
104
114
  level: "error",
105
115
  event: "engine.provider_init_error",
@@ -110,7 +120,7 @@ function getProviderRuntime() {
110
120
  process.exit(1);
111
121
  throw new Error("unreachable");
112
122
  }
113
- return _providerRuntime.runtime;
123
+ return _providerRuntimes[facing].runtime;
114
124
  }
115
125
  /**
116
126
  * Clear the cached provider runtime so the next access re-creates it from
@@ -118,17 +128,18 @@ function getProviderRuntime() {
118
128
  * provider fingerprint changes on disk.
119
129
  */
120
130
  function resetProviderRuntime() {
121
- _providerRuntime = null;
131
+ _providerRuntimes.human = null;
132
+ _providerRuntimes.agent = null;
122
133
  }
123
- function getModel() {
124
- return getProviderRuntime().model;
134
+ function getModel(facing = "human") {
135
+ return getProviderRuntime(facing).model;
125
136
  }
126
- function getProvider() {
127
- return getProviderRuntime().id;
137
+ function getProvider(facing = "human") {
138
+ return getProviderRuntime(facing).id;
128
139
  }
129
- function createSummarize() {
140
+ function createSummarize(facing = "human") {
130
141
  return async (transcript, instruction) => {
131
- const runtime = getProviderRuntime();
142
+ const runtime = getProviderRuntime(facing);
132
143
  const client = runtime.client;
133
144
  const response = await client.chat.completions.create({
134
145
  model: runtime.model,
@@ -141,18 +152,21 @@ function createSummarize() {
141
152
  return response.choices?.[0]?.message?.content ?? transcript;
142
153
  };
143
154
  }
144
- function getProviderDisplayLabel() {
145
- const provider = (0, identity_1.loadAgentConfig)().provider;
155
+ function getProviderDisplayLabel(facing = "human") {
156
+ const config = (0, identity_1.loadAgentConfig)();
157
+ const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
158
+ const provider = facingConfig.provider;
159
+ const model = facingConfig.model || "unknown";
146
160
  const providerLabelBuilders = {
147
161
  azure: () => {
148
- const config = (0, config_1.getAzureConfig)();
149
- return `azure openai (${config.deployment || "default"}, model: ${config.modelName || "unknown"})`;
162
+ const azureCfg = (0, config_1.getAzureConfig)();
163
+ return `azure openai (${azureCfg.deployment || "default"}, model: ${model})`;
150
164
  },
151
- anthropic: () => `anthropic (${(0, config_1.getAnthropicConfig)().model || "unknown"})`,
152
- minimax: () => `minimax (${(0, config_1.getMinimaxConfig)().model || "unknown"})`,
153
- "openai-codex": () => `openai codex (${(0, config_1.getOpenAICodexConfig)().model || "unknown"})`,
165
+ anthropic: () => `anthropic (${model})`,
166
+ minimax: () => `minimax (${model})`,
167
+ "openai-codex": () => `openai codex (${model})`,
154
168
  /* v8 ignore next -- branch: tested via display label unit test @preserve */
155
- "github-copilot": () => `github copilot (${(0, config_1.getGithubCopilotConfig)().model || "unknown"})`,
169
+ "github-copilot": () => `github copilot (${model})`,
156
170
  };
157
171
  return providerLabelBuilders[provider]();
158
172
  }
@@ -431,7 +445,8 @@ const TRANSIENT_RETRY_LABELS = {
431
445
  "unknown": "error",
432
446
  };
433
447
  async function runAgent(messages, callbacks, channel, signal, options) {
434
- const providerRuntime = getProviderRuntime();
448
+ const facing = (0, channel_1.channelToFacing)(channel);
449
+ const providerRuntime = getProviderRuntime(facing);
435
450
  const provider = providerRuntime.id;
436
451
  const toolChoiceRequired = options?.toolChoiceRequired ?? true;
437
452
  const traceId = options?.traceId;
@@ -47,6 +47,7 @@ const os = __importStar(require("os"));
47
47
  const path = __importStar(require("path"));
48
48
  const runtime_1 = require("../../nerves/runtime");
49
49
  const identity_1 = require("../identity");
50
+ const migrate_config_1 = require("../migrate-config");
50
51
  const ANTHROPIC_SETUP_TOKEN_PREFIX = "sk-ant-oat01-";
51
52
  const ANTHROPIC_SETUP_TOKEN_MIN_LENGTH = 80;
52
53
  const DEFAULT_SECRETS_TEMPLATE = {
@@ -129,9 +130,22 @@ function readJsonRecord(filePath, label) {
129
130
  }
130
131
  }
131
132
  function readAgentConfigForAgent(agentName, bundlesRoot = (0, identity_1.getAgentBundlesRoot)()) {
132
- const configPath = path.join(bundlesRoot, `${agentName}.ouro`, "agent.json");
133
- const parsed = readJsonRecord(configPath, "agent config");
134
- const provider = parsed.provider;
133
+ const agentRoot = path.join(bundlesRoot, `${agentName}.ouro`);
134
+ const configPath = path.join(agentRoot, "agent.json");
135
+ let parsed = readJsonRecord(configPath, "agent config");
136
+ // Inline migration: v1 -> v2
137
+ const version = typeof parsed.version === "number" ? parsed.version : 1;
138
+ if (version < 2) {
139
+ (0, migrate_config_1.migrateAgentConfigV1ToV2)(agentRoot);
140
+ parsed = readJsonRecord(configPath, "agent config");
141
+ }
142
+ // Validate v2 required facing fields
143
+ const humanFacing = parsed.humanFacing;
144
+ const agentFacing = parsed.agentFacing;
145
+ if (!humanFacing || typeof humanFacing !== "object") {
146
+ throw new Error(`agent.json at ${configPath} has unsupported provider '${String(parsed.provider)}'`);
147
+ }
148
+ const provider = humanFacing.provider;
135
149
  if (provider !== "azure" &&
136
150
  provider !== "anthropic" &&
137
151
  provider !== "minimax" &&
@@ -139,20 +153,27 @@ function readAgentConfigForAgent(agentName, bundlesRoot = (0, identity_1.getAgen
139
153
  provider !== "github-copilot") {
140
154
  throw new Error(`agent.json at ${configPath} has unsupported provider '${String(provider)}'`);
141
155
  }
156
+ if (!agentFacing || typeof agentFacing !== "object") {
157
+ throw new Error(`agent.json at ${configPath} has unsupported provider '${String(parsed.provider)}'`);
158
+ }
142
159
  return {
143
160
  configPath,
144
161
  config: parsed,
145
162
  };
146
163
  }
147
- function writeAgentProviderSelection(agentName, provider, bundlesRoot = (0, identity_1.getAgentBundlesRoot)()) {
164
+ function writeAgentProviderSelection(agentName, facing, provider, bundlesRoot = (0, identity_1.getAgentBundlesRoot)()) {
148
165
  const { configPath, config } = readAgentConfigForAgent(agentName, bundlesRoot);
149
- const nextConfig = { ...config, provider };
166
+ const facingKey = facing === "human" ? "humanFacing" : "agentFacing";
167
+ const nextConfig = {
168
+ ...config,
169
+ [facingKey]: { ...config[facingKey], provider },
170
+ };
150
171
  fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
151
172
  (0, runtime_1.emitNervesEvent)({
152
173
  component: "daemon",
153
174
  event: "daemon.auth_provider_selected",
154
175
  message: "updated agent provider selection after auth flow",
155
- meta: { agentName, provider, configPath },
176
+ meta: { agentName, facing, provider, configPath },
156
177
  });
157
178
  return configPath;
158
179
  }
@@ -189,25 +210,24 @@ function writeProviderCredentials(agentName, provider, credentials, deps = {}) {
189
210
  writeSecrets(secretsPath, secrets);
190
211
  return { secretsPath, secrets };
191
212
  }
192
- const MODEL_FIELD = {
193
- azure: "modelName",
194
- minimax: "model",
195
- anthropic: "model",
196
- "openai-codex": "model",
197
- "github-copilot": "model",
198
- };
199
- function writeAgentModel(agentName, modelName, deps = {}) {
200
- const { config } = readAgentConfigForAgent(agentName, deps.bundlesRoot);
201
- const provider = config.provider;
202
- const { secretsPath, secrets } = loadAgentSecrets(agentName, deps);
203
- const providerSecrets = secrets.providers[provider];
204
- /* v8 ignore next -- fallback: all known providers are in MODEL_FIELD @preserve */
205
- const fieldName = MODEL_FIELD[provider] ?? "model";
206
- /* v8 ignore next -- defensive: fieldName always exists in template @preserve */
207
- const previousModel = providerSecrets[fieldName] ?? "";
208
- providerSecrets[fieldName] = modelName;
209
- writeSecrets(secretsPath, secrets);
210
- return { secretsPath, provider, previousModel };
213
+ function writeAgentModel(agentName, facing, modelName, deps = {}) {
214
+ const { configPath, config } = readAgentConfigForAgent(agentName, deps.bundlesRoot);
215
+ const facingKey = facing === "human" ? "humanFacing" : "agentFacing";
216
+ const facingBlock = config[facingKey];
217
+ const previousModel = facingBlock.model;
218
+ const provider = facingBlock.provider;
219
+ const nextConfig = {
220
+ ...config,
221
+ [facingKey]: { ...facingBlock, model: modelName },
222
+ };
223
+ fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
224
+ (0, runtime_1.emitNervesEvent)({
225
+ component: "daemon",
226
+ event: "daemon.config_model_updated",
227
+ message: "updated agent model in agent.json",
228
+ meta: { agentName, facing, provider, modelName, previousModel, configPath },
229
+ });
230
+ return { configPath, provider, previousModel };
211
231
  }
212
232
  function readCodexAccessToken(homeDir) {
213
233
  const authPath = path.join(homeDir, ".codex", "auth.json");
@@ -65,6 +65,7 @@ const daemon_runtime_sync_1 = require("./daemon-runtime-sync");
65
65
  const agent_discovery_1 = require("./agent-discovery");
66
66
  const update_hooks_1 = require("./update-hooks");
67
67
  const bundle_meta_1 = require("./hooks/bundle-meta");
68
+ const agent_config_v2_1 = require("./hooks/agent-config-v2");
68
69
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
69
70
  const tasks_1 = require("../../repertoire/tasks");
70
71
  const thoughts_1 = require("./thoughts");
@@ -305,6 +306,7 @@ function usage() {
305
306
  " ouro task create <title> [--type <type>] [--agent <name>]",
306
307
  " ouro task update <id> <status> [--agent <name>]",
307
308
  " ouro task show <id> [--agent <name>]",
309
+ " ouro task fix [--safe|--all] [<id> [--option <N>]] [--agent <name>]",
308
310
  " ouro task actionable|deps|sessions [--agent <name>]",
309
311
  " ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>] [--agent <name>]",
310
312
  " ouro friend list [--agent <name>]",
@@ -737,14 +739,51 @@ function parseTaskCommand(args) {
737
739
  return { kind: "task.deps", ...(agent ? { agent } : {}) };
738
740
  if (sub === "sessions")
739
741
  return { kind: "task.sessions", ...(agent ? { agent } : {}) };
742
+ if (sub === "fix") {
743
+ // fix --safe | fix --all | fix <id> [--option N] | fix (dry-run)
744
+ if (rest.length === 0)
745
+ return { kind: "task.fix", mode: "dry-run", ...(agent ? { agent } : {}) };
746
+ const first = rest[0];
747
+ if (first === "--safe" || first === "--all") {
748
+ return { kind: "task.fix", mode: "safe", ...(agent ? { agent } : {}) };
749
+ }
750
+ // first arg is an issue ID (contains a colon, e.g. schema-missing-kind:one-shots/foo.md)
751
+ const issueId = first;
752
+ let option;
753
+ for (let i = 1; i < rest.length; i++) {
754
+ if (rest[i] === "--option" && rest[i + 1]) {
755
+ option = parseInt(rest[i + 1], 10);
756
+ i += 1;
757
+ }
758
+ }
759
+ return {
760
+ kind: "task.fix",
761
+ mode: "single",
762
+ issueId,
763
+ ...(option !== undefined ? { option } : {}),
764
+ ...(agent ? { agent } : {}),
765
+ };
766
+ }
740
767
  throw new Error(`Usage\n${usage()}`);
741
768
  }
769
+ function extractFacingFlag(args) {
770
+ const idx = args.indexOf("--facing");
771
+ if (idx === -1 || idx + 1 >= args.length)
772
+ return { rest: args };
773
+ const value = args[idx + 1];
774
+ if (value !== "human" && value !== "agent") {
775
+ throw new Error(`--facing must be 'human' or 'agent'`);
776
+ }
777
+ const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
778
+ return { facing: value, rest };
779
+ }
742
780
  function parseAuthCommand(args) {
743
781
  const first = args[0];
744
782
  // Support both positional (`auth switch`) and flag (`auth --switch`) forms
745
783
  if (first === "verify" || first === "switch" || first === "--verify" || first === "--switch") {
746
784
  const subcommand = first.replace(/^--/, "");
747
- const { agent, rest } = extractAgentFlag(args.slice(1));
785
+ const { agent, rest: afterAgent } = extractAgentFlag(args.slice(1));
786
+ const { facing, rest } = extractFacingFlag(afterAgent);
748
787
  let provider;
749
788
  /* v8 ignore start -- provider flag parsing: branches tested via CLI parsing tests @preserve */
750
789
  for (let i = 0; i < rest.length; i += 1) {
@@ -764,7 +803,7 @@ function parseAuthCommand(args) {
764
803
  if (subcommand === "switch") {
765
804
  if (!provider)
766
805
  throw new Error(`auth switch requires --provider.\n${usage()}`);
767
- return { kind: "auth.switch", agent, provider };
806
+ return facing ? { kind: "auth.switch", agent, provider, facing } : { kind: "auth.switch", agent, provider };
768
807
  }
769
808
  return provider ? { kind: "auth.verify", agent, provider } : { kind: "auth.verify", agent };
770
809
  }
@@ -946,7 +985,8 @@ function parseFriendCommand(args) {
946
985
  throw new Error(`Usage\n${usage()}`);
947
986
  }
948
987
  function parseConfigCommand(args) {
949
- const { agent, rest: cleaned } = extractAgentFlag(args);
988
+ const { agent, rest: afterAgent } = extractAgentFlag(args);
989
+ const { facing, rest: cleaned } = extractFacingFlag(afterAgent);
950
990
  const [sub, ...rest] = cleaned;
951
991
  if (!sub)
952
992
  throw new Error(`Usage\n${usage()}`);
@@ -956,7 +996,7 @@ function parseConfigCommand(args) {
956
996
  const modelName = rest[0];
957
997
  if (!modelName)
958
998
  throw new Error(`Usage: ouro config model --agent <name> <model-name>`);
959
- return { kind: "config.model", agent, modelName };
999
+ return facing ? { kind: "config.model", agent, modelName, facing } : { kind: "config.model", agent, modelName };
960
1000
  }
961
1001
  if (sub === "models") {
962
1002
  if (!agent)
@@ -1439,9 +1479,11 @@ async function defaultRunAdoptionSpecialist() {
1439
1479
  const { loadIdentityPhrases } = await Promise.resolve().then(() => __importStar(require("./specialist-orchestrator")));
1440
1480
  const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
1441
1481
  setAgentConfigOverride({
1442
- version: 1,
1482
+ version: 2,
1443
1483
  enabled: true,
1444
1484
  provider: providerRaw,
1485
+ humanFacing: { provider: providerRaw, model: "" },
1486
+ agentFacing: { provider: providerRaw, model: "" },
1445
1487
  phrases,
1446
1488
  });
1447
1489
  patchRuntimeConfig({
@@ -1453,6 +1495,7 @@ async function defaultRunAdoptionSpecialist() {
1453
1495
  const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
1454
1496
  tempDir,
1455
1497
  provider: providerRaw,
1498
+ model: providerConfig.model ?? "",
1456
1499
  });
1457
1500
  // Build specialist tools
1458
1501
  const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
@@ -1796,6 +1839,60 @@ function executeTaskCommand(command, taskMod) {
1796
1839
  const lines = taskMod.boardDeps();
1797
1840
  return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
1798
1841
  }
1842
+ if (command.kind === "task.fix") {
1843
+ try {
1844
+ const fixOptions = {
1845
+ mode: command.mode,
1846
+ ...(command.issueId ? { issueId: command.issueId } : {}),
1847
+ ...(command.option !== undefined ? { option: command.option } : {}),
1848
+ };
1849
+ const result = taskMod.fix(fixOptions);
1850
+ if (command.mode === "dry-run") {
1851
+ if (result.remaining.length === 0) {
1852
+ return `task health: clean`;
1853
+ }
1854
+ const safeIssues = result.remaining.filter((i) => i.confidence === "safe");
1855
+ const reviewIssues = result.remaining.filter((i) => i.confidence === "needs_review");
1856
+ const lines = [`${result.remaining.length} issues found`];
1857
+ if (safeIssues.length > 0) {
1858
+ lines.push("", `safe fixes (${safeIssues.length}):`);
1859
+ for (const issue of safeIssues) {
1860
+ lines.push(` ${issue.code}:${issue.target} -- ${issue.description}`);
1861
+ }
1862
+ }
1863
+ if (reviewIssues.length > 0) {
1864
+ lines.push("", `needs review (${reviewIssues.length}):`);
1865
+ for (const issue of reviewIssues) {
1866
+ lines.push(` ${issue.code}:${issue.target} -- ${issue.description}`);
1867
+ }
1868
+ }
1869
+ lines.push("", `task health: ${result.health}`);
1870
+ return lines.join("\n");
1871
+ }
1872
+ // safe, single, or --all modes: show what was done
1873
+ const lines = [];
1874
+ if (result.applied.length > 0) {
1875
+ lines.push(`${result.applied.length} applied:`);
1876
+ for (const issue of result.applied) {
1877
+ lines.push(` ${issue.code}:${issue.target}`);
1878
+ }
1879
+ }
1880
+ if (result.remaining.length > 0) {
1881
+ lines.push(`${result.remaining.length} remaining:`);
1882
+ for (const issue of result.remaining) {
1883
+ lines.push(` ${issue.code}:${issue.target} -- ${issue.description}`);
1884
+ }
1885
+ }
1886
+ if (result.applied.length === 0 && result.remaining.length === 0) {
1887
+ lines.push("no issues");
1888
+ }
1889
+ lines.push(`task health: ${result.health}`);
1890
+ return lines.join("\n");
1891
+ }
1892
+ catch (error) {
1893
+ return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
1894
+ }
1895
+ }
1799
1896
  // command.kind === "task.sessions"
1800
1897
  const lines = taskMod.boardSessions();
1801
1898
  return lines.length > 0 ? lines.join("\n") : "no active sessions";
@@ -2182,6 +2279,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2182
2279
  }
2183
2280
  // Run update hooks before starting daemon so user sees the output
2184
2281
  (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
2282
+ (0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
2185
2283
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
2186
2284
  const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
2187
2285
  // Snapshot the previous CLI version from the first bundle-meta before
@@ -2580,7 +2678,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2580
2678
  // ── task subcommands (local, no daemon socket needed) ──
2581
2679
  if (command.kind === "task.board" || command.kind === "task.create" || command.kind === "task.update" ||
2582
2680
  command.kind === "task.show" || command.kind === "task.actionable" || command.kind === "task.deps" ||
2583
- command.kind === "task.sessions") {
2681
+ command.kind === "task.sessions" || command.kind === "task.fix") {
2584
2682
  /* v8 ignore start -- production default: requires full identity setup @preserve */
2585
2683
  const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
2586
2684
  /* v8 ignore stop */
@@ -2672,7 +2770,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2672
2770
  }
2673
2771
  // ── auth (local, no daemon socket needed) ──
2674
2772
  if (command.kind === "auth.run") {
2675
- const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent).config.provider;
2773
+ const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent).config.humanFacing.provider;
2676
2774
  /* v8 ignore next -- tests always inject runAuthFlow; default is for production @preserve */
2677
2775
  const authRunner = deps.runAuthFlow ?? auth_flow_1.runRuntimeAuthFlow;
2678
2776
  const result = await authRunner({
@@ -2732,7 +2830,13 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2732
2830
  deps.writeStdout(message);
2733
2831
  return message;
2734
2832
  }
2735
- (0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.provider);
2833
+ if (command.facing) {
2834
+ (0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.facing, command.provider);
2835
+ }
2836
+ else {
2837
+ (0, auth_flow_1.writeAgentProviderSelection)(command.agent, "human", command.provider);
2838
+ (0, auth_flow_1.writeAgentProviderSelection)(command.agent, "agent", command.provider);
2839
+ }
2736
2840
  const message = `switched ${command.agent} to ${command.provider} (verified working)`;
2737
2841
  deps.writeStdout(message);
2738
2842
  return message;
@@ -2742,7 +2846,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2742
2846
  /* v8 ignore start -- config models: tested via daemon-cli.test.ts @preserve */
2743
2847
  if (command.kind === "config.models") {
2744
2848
  const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent);
2745
- const provider = config.provider;
2849
+ const provider = config.humanFacing.provider;
2746
2850
  if (provider !== "github-copilot") {
2747
2851
  const message = `model listing not available for ${provider} — check provider documentation.`;
2748
2852
  deps.writeStdout(message);
@@ -2773,9 +2877,11 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2773
2877
  // ── config model (local, no daemon socket needed) ──
2774
2878
  /* v8 ignore start -- config model: tested via daemon-cli.test.ts @preserve */
2775
2879
  if (command.kind === "config.model") {
2880
+ const facing = command.facing ?? "human";
2776
2881
  // Validate model availability for github-copilot before writing
2777
2882
  const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent);
2778
- if (config.provider === "github-copilot") {
2883
+ const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
2884
+ if (facingConfig.provider === "github-copilot") {
2779
2885
  const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
2780
2886
  const ghConfig = secrets.providers["github-copilot"];
2781
2887
  if (ghConfig.githubToken && ghConfig.baseUrl) {
@@ -2801,7 +2907,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2801
2907
  }
2802
2908
  }
2803
2909
  }
2804
- const { provider, previousModel } = (0, auth_flow_1.writeAgentModel)(command.agent, command.modelName);
2910
+ const { provider, previousModel } = (0, auth_flow_1.writeAgentModel)(command.agent, facing, command.modelName);
2805
2911
  const message = previousModel
2806
2912
  ? `updated ${command.agent} model on ${provider}: ${previousModel} → ${command.modelName}`
2807
2913
  : `set ${command.agent} model on ${provider}: ${command.modelName}`;
@@ -46,6 +46,7 @@ const runtime_metadata_1 = require("./runtime-metadata");
46
46
  const runtime_mode_1 = require("./runtime-mode");
47
47
  const update_hooks_1 = require("./update-hooks");
48
48
  const bundle_meta_1 = require("./hooks/bundle-meta");
49
+ const agent_config_v2_1 = require("./hooks/agent-config-v2");
49
50
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
50
51
  const update_checker_1 = require("./update-checker");
51
52
  const staged_restart_1 = require("./staged-restart");
@@ -200,6 +201,7 @@ class OuroDaemon {
200
201
  });
201
202
  // Register update hooks and apply pending updates before starting agents
202
203
  (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
204
+ (0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
203
205
  const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
204
206
  await (0, update_hooks_1.applyPendingUpdates)(this.bundlesRoot, currentVersion);
205
207
  // Start periodic update checker (polls npm registry every 30 minutes)