@ouro.bot/cli 0.1.0-alpha.12 → 0.1.0-alpha.121

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 (130) hide show
  1. package/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
  2. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  3. package/README.md +147 -205
  4. package/assets/ouroboros.png +0 -0
  5. package/changelog.json +737 -0
  6. package/dist/heart/active-work.js +622 -0
  7. package/dist/heart/bridges/manager.js +358 -0
  8. package/dist/heart/bridges/state-machine.js +135 -0
  9. package/dist/heart/bridges/store.js +123 -0
  10. package/dist/heart/commitments.js +105 -0
  11. package/dist/heart/config.js +68 -23
  12. package/dist/heart/core.js +528 -100
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/auth-flow.js +430 -0
  16. package/dist/heart/daemon/daemon-cli.js +1601 -207
  17. package/dist/heart/daemon/daemon-entry.js +43 -2
  18. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  19. package/dist/heart/daemon/daemon.js +226 -1
  20. package/dist/heart/daemon/hatch-animation.js +10 -3
  21. package/dist/heart/daemon/hatch-flow.js +7 -82
  22. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  23. package/dist/heart/daemon/launchd.js +159 -0
  24. package/dist/heart/daemon/log-tailer.js +4 -3
  25. package/dist/heart/daemon/message-router.js +17 -8
  26. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  27. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  28. package/dist/heart/daemon/ouro-entry.js +0 -0
  29. package/dist/heart/daemon/ouro-path-installer.js +59 -15
  30. package/dist/heart/daemon/ouro-uti.js +11 -2
  31. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  32. package/dist/heart/daemon/process-manager.js +13 -0
  33. package/dist/heart/daemon/run-hooks.js +37 -0
  34. package/dist/heart/daemon/runtime-logging.js +58 -15
  35. package/dist/heart/daemon/runtime-metadata.js +219 -0
  36. package/dist/heart/daemon/runtime-mode.js +67 -0
  37. package/dist/heart/daemon/sense-manager.js +43 -2
  38. package/dist/heart/daemon/skill-management-installer.js +94 -0
  39. package/dist/heart/daemon/socket-client.js +202 -0
  40. package/dist/heart/daemon/specialist-orchestrator.js +37 -94
  41. package/dist/heart/daemon/specialist-prompt.js +50 -12
  42. package/dist/heart/daemon/specialist-tools.js +211 -60
  43. package/dist/heart/daemon/staged-restart.js +114 -0
  44. package/dist/heart/daemon/thoughts.js +507 -0
  45. package/dist/heart/daemon/update-checker.js +111 -0
  46. package/dist/heart/daemon/update-hooks.js +138 -0
  47. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  48. package/dist/heart/delegation.js +62 -0
  49. package/dist/heart/identity.js +64 -21
  50. package/dist/heart/kicks.js +1 -19
  51. package/dist/heart/model-capabilities.js +48 -0
  52. package/dist/heart/obligations.js +197 -0
  53. package/dist/heart/progress-story.js +42 -0
  54. package/dist/heart/provider-failover.js +88 -0
  55. package/dist/heart/provider-ping.js +151 -0
  56. package/dist/heart/providers/anthropic.js +107 -20
  57. package/dist/heart/providers/azure.js +115 -9
  58. package/dist/heart/providers/github-copilot.js +157 -0
  59. package/dist/heart/providers/minimax.js +33 -3
  60. package/dist/heart/providers/openai-codex.js +49 -14
  61. package/dist/heart/safe-workspace.js +381 -0
  62. package/dist/heart/session-activity.js +169 -0
  63. package/dist/heart/session-recall.js +216 -0
  64. package/dist/heart/streaming.js +108 -24
  65. package/dist/heart/target-resolution.js +123 -0
  66. package/dist/heart/tool-loop.js +194 -0
  67. package/dist/heart/turn-coordinator.js +28 -0
  68. package/dist/mind/associative-recall.js +14 -2
  69. package/dist/mind/bundle-manifest.js +70 -0
  70. package/dist/mind/context.js +60 -14
  71. package/dist/mind/first-impressions.js +16 -2
  72. package/dist/mind/friends/channel.js +35 -0
  73. package/dist/mind/friends/group-context.js +144 -0
  74. package/dist/mind/friends/store-file.js +19 -0
  75. package/dist/mind/friends/trust-explanation.js +74 -0
  76. package/dist/mind/friends/types.js +8 -0
  77. package/dist/mind/memory.js +27 -26
  78. package/dist/mind/obligation-steering.js +221 -0
  79. package/dist/mind/pending.js +76 -9
  80. package/dist/mind/phrases.js +1 -0
  81. package/dist/mind/prompt.js +459 -77
  82. package/dist/mind/token-estimate.js +8 -12
  83. package/dist/nerves/cli-logging.js +15 -2
  84. package/dist/nerves/coverage/run-artifacts.js +1 -1
  85. package/dist/nerves/index.js +12 -0
  86. package/dist/repertoire/ado-client.js +4 -2
  87. package/dist/repertoire/coding/context-pack.js +254 -0
  88. package/dist/repertoire/coding/feedback.js +301 -0
  89. package/dist/repertoire/coding/index.js +4 -1
  90. package/dist/repertoire/coding/manager.js +210 -4
  91. package/dist/repertoire/coding/spawner.js +39 -9
  92. package/dist/repertoire/coding/tools.js +171 -4
  93. package/dist/repertoire/data/ado-endpoints.json +188 -0
  94. package/dist/repertoire/guardrails.js +290 -0
  95. package/dist/repertoire/mcp-client.js +254 -0
  96. package/dist/repertoire/mcp-manager.js +195 -0
  97. package/dist/repertoire/skills.js +3 -26
  98. package/dist/repertoire/tasks/board.js +12 -0
  99. package/dist/repertoire/tasks/index.js +23 -9
  100. package/dist/repertoire/tasks/transitions.js +1 -2
  101. package/dist/repertoire/tools-base.js +925 -250
  102. package/dist/repertoire/tools-bluebubbles.js +93 -0
  103. package/dist/repertoire/tools-teams.js +58 -25
  104. package/dist/repertoire/tools.js +106 -53
  105. package/dist/senses/bluebubbles-client.js +210 -5
  106. package/dist/senses/bluebubbles-entry.js +2 -0
  107. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  108. package/dist/senses/bluebubbles-media.js +339 -0
  109. package/dist/senses/bluebubbles-model.js +12 -4
  110. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  111. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  112. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  113. package/dist/senses/bluebubbles.js +912 -45
  114. package/dist/senses/cli-layout.js +187 -0
  115. package/dist/senses/cli.js +477 -170
  116. package/dist/senses/continuity.js +94 -0
  117. package/dist/senses/debug-activity.js +154 -0
  118. package/dist/senses/inner-dialog-worker.js +47 -18
  119. package/dist/senses/inner-dialog.js +388 -83
  120. package/dist/senses/pipeline.js +444 -0
  121. package/dist/senses/teams.js +607 -129
  122. package/dist/senses/trust-gate.js +112 -2
  123. package/package.json +14 -3
  124. package/subagents/README.md +4 -70
  125. package/dist/heart/daemon/specialist-session.js +0 -177
  126. package/dist/heart/daemon/subagent-installer.js +0 -134
  127. package/dist/inner-worker-entry.js +0 -4
  128. package/subagents/work-doer.md +0 -233
  129. package/subagents/work-merger.md +0 -624
  130. package/subagents/work-planner.md +0 -373
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isOpenObligationStatus = isOpenObligationStatus;
37
+ exports.isOpenObligation = isOpenObligation;
38
+ exports.createObligation = createObligation;
39
+ exports.readObligations = readObligations;
40
+ exports.readPendingObligations = readPendingObligations;
41
+ exports.advanceObligation = advanceObligation;
42
+ exports.fulfillObligation = fulfillObligation;
43
+ exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const runtime_1 = require("../nerves/runtime");
47
+ function obligationsDir(agentRoot) {
48
+ return path.join(agentRoot, "state", "obligations");
49
+ }
50
+ function obligationFilePath(agentRoot, id) {
51
+ return path.join(obligationsDir(agentRoot), `${id}.json`);
52
+ }
53
+ function generateId() {
54
+ const timestamp = Date.now();
55
+ const random = Math.random().toString(36).slice(2, 10);
56
+ return `${timestamp}-${random}`;
57
+ }
58
+ function isOpenObligationStatus(status) {
59
+ return status !== "fulfilled";
60
+ }
61
+ function isOpenObligation(obligation) {
62
+ return isOpenObligationStatus(obligation.status);
63
+ }
64
+ function createObligation(agentRoot, input) {
65
+ const now = new Date().toISOString();
66
+ const id = generateId();
67
+ const obligation = {
68
+ id,
69
+ origin: input.origin,
70
+ ...(input.bridgeId ? { bridgeId: input.bridgeId } : {}),
71
+ content: input.content,
72
+ status: "pending",
73
+ createdAt: now,
74
+ updatedAt: now,
75
+ };
76
+ const dir = obligationsDir(agentRoot);
77
+ fs.mkdirSync(dir, { recursive: true });
78
+ fs.writeFileSync(obligationFilePath(agentRoot, id), JSON.stringify(obligation, null, 2), "utf-8");
79
+ (0, runtime_1.emitNervesEvent)({
80
+ component: "engine",
81
+ event: "engine.obligation_created",
82
+ message: "obligation created",
83
+ meta: {
84
+ obligationId: id,
85
+ friendId: input.origin.friendId,
86
+ channel: input.origin.channel,
87
+ key: input.origin.key,
88
+ },
89
+ });
90
+ return obligation;
91
+ }
92
+ function readObligations(agentRoot) {
93
+ const dir = obligationsDir(agentRoot);
94
+ if (!fs.existsSync(dir))
95
+ return [];
96
+ let entries;
97
+ try {
98
+ entries = fs.readdirSync(dir);
99
+ }
100
+ catch {
101
+ /* v8 ignore next -- defensive: readdirSync race after existsSync @preserve */
102
+ return [];
103
+ }
104
+ const jsonFiles = entries.filter((entry) => entry.endsWith(".json")).sort();
105
+ const obligations = [];
106
+ for (const file of jsonFiles) {
107
+ try {
108
+ const raw = fs.readFileSync(path.join(dir, file), "utf-8");
109
+ const parsed = JSON.parse(raw);
110
+ if (typeof parsed.id === "string" && typeof parsed.content === "string") {
111
+ obligations.push(parsed);
112
+ }
113
+ }
114
+ catch {
115
+ // skip malformed files
116
+ }
117
+ }
118
+ return obligations;
119
+ }
120
+ function readPendingObligations(agentRoot) {
121
+ return readObligations(agentRoot).filter(isOpenObligation);
122
+ }
123
+ function advanceObligation(agentRoot, obligationId, update) {
124
+ const filePath = obligationFilePath(agentRoot, obligationId);
125
+ let obligation;
126
+ try {
127
+ const raw = fs.readFileSync(filePath, "utf-8");
128
+ obligation = JSON.parse(raw);
129
+ }
130
+ catch {
131
+ return;
132
+ }
133
+ const previousStatus = obligation.status;
134
+ if (update.status) {
135
+ obligation.status = update.status;
136
+ if (update.status === "fulfilled") {
137
+ obligation.fulfilledAt = new Date().toISOString();
138
+ }
139
+ }
140
+ if (update.currentSurface) {
141
+ obligation.currentSurface = update.currentSurface;
142
+ }
143
+ if (typeof update.currentArtifact === "string") {
144
+ obligation.currentArtifact = update.currentArtifact;
145
+ }
146
+ if (typeof update.nextAction === "string") {
147
+ obligation.nextAction = update.nextAction;
148
+ }
149
+ if (typeof update.latestNote === "string") {
150
+ obligation.latestNote = update.latestNote;
151
+ }
152
+ obligation.updatedAt = new Date().toISOString();
153
+ fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf-8");
154
+ (0, runtime_1.emitNervesEvent)({
155
+ component: "engine",
156
+ event: "engine.obligation_advanced",
157
+ message: "obligation advanced",
158
+ meta: {
159
+ obligationId,
160
+ previousStatus,
161
+ status: obligation.status,
162
+ friendId: obligation.origin.friendId,
163
+ channel: obligation.origin.channel,
164
+ key: obligation.origin.key,
165
+ surfaceKind: obligation.currentSurface?.kind ?? null,
166
+ surfaceLabel: obligation.currentSurface?.label ?? null,
167
+ },
168
+ });
169
+ }
170
+ function fulfillObligation(agentRoot, obligationId) {
171
+ advanceObligation(agentRoot, obligationId, { status: "fulfilled" });
172
+ const filePath = obligationFilePath(agentRoot, obligationId);
173
+ let obligation;
174
+ try {
175
+ const raw = fs.readFileSync(filePath, "utf-8");
176
+ obligation = JSON.parse(raw);
177
+ }
178
+ catch {
179
+ return;
180
+ }
181
+ (0, runtime_1.emitNervesEvent)({
182
+ component: "engine",
183
+ event: "engine.obligation_fulfilled",
184
+ message: "obligation fulfilled",
185
+ meta: {
186
+ obligationId,
187
+ friendId: obligation.origin.friendId,
188
+ channel: obligation.origin.channel,
189
+ key: obligation.origin.key,
190
+ },
191
+ });
192
+ }
193
+ function findPendingObligationForOrigin(agentRoot, origin) {
194
+ return readPendingObligations(agentRoot).find((ob) => ob.origin.friendId === origin.friendId
195
+ && ob.origin.channel === origin.channel
196
+ && ob.origin.key === origin.key);
197
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildProgressStory = buildProgressStory;
4
+ exports.renderProgressStory = renderProgressStory;
5
+ const runtime_1 = require("../nerves/runtime");
6
+ function labelForScope(scope) {
7
+ return scope === "inner-delegation" ? "inner work" : "shared work";
8
+ }
9
+ function compactDetail(text) {
10
+ if (typeof text !== "string")
11
+ return null;
12
+ const trimmed = text.trim();
13
+ return trimmed.length > 0 ? trimmed : null;
14
+ }
15
+ function buildProgressStory(input) {
16
+ const detailLines = [
17
+ compactDetail(input.objective),
18
+ compactDetail(input.outcomeText),
19
+ compactDetail(input.bridgeId ? `bridge: ${input.bridgeId}` : null),
20
+ compactDetail(input.taskName ? `task: ${input.taskName}` : null),
21
+ ].filter((line) => Boolean(line));
22
+ const story = {
23
+ statusLine: `${labelForScope(input.scope)}: ${input.phase}`,
24
+ detailLines,
25
+ };
26
+ (0, runtime_1.emitNervesEvent)({
27
+ component: "engine",
28
+ event: "engine.progress_story_build",
29
+ message: "built shared progress story",
30
+ meta: {
31
+ scope: input.scope,
32
+ phase: input.phase,
33
+ detailLines: detailLines.length,
34
+ hasBridge: Boolean(input.bridgeId),
35
+ hasTask: Boolean(input.taskName),
36
+ },
37
+ });
38
+ return story;
39
+ }
40
+ function renderProgressStory(story) {
41
+ return [story.statusLine, ...story.detailLines].join("\n");
42
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFailoverContext = buildFailoverContext;
4
+ exports.handleFailoverReply = handleFailoverReply;
5
+ const runtime_1 = require("../nerves/runtime");
6
+ const FAILING_PROVIDER_LABELS = {
7
+ "auth-failure": "its credentials need to be refreshed",
8
+ "usage-limit": "has also hit its usage limit",
9
+ "rate-limit": "is also being rate limited",
10
+ "server-error": "is also experiencing an outage",
11
+ "network-error": "could not be reached",
12
+ "unknown": "could not be reached",
13
+ };
14
+ const CLASSIFICATION_LABELS = {
15
+ "auth-failure": "authentication failed",
16
+ "usage-limit": "hit its usage limit",
17
+ "rate-limit": "is being rate limited",
18
+ "server-error": "is experiencing an outage",
19
+ "network-error": "is unreachable (network error)",
20
+ "unknown": "encountered an error",
21
+ };
22
+ function buildFailoverContext(_errorMessage, classification, currentProvider, currentModel, agentName, inventory, providerModels) {
23
+ const label = CLASSIFICATION_LABELS[classification];
24
+ const providerWithModel = currentModel ? `${currentProvider} (${currentModel})` : currentProvider;
25
+ const errorSummary = `${providerWithModel} ${label}`;
26
+ const workingProviders = [];
27
+ const unconfiguredProviders = [];
28
+ const failingProviders = [];
29
+ for (const [provider, result] of Object.entries(inventory)) {
30
+ if (result.ok) {
31
+ workingProviders.push(provider);
32
+ }
33
+ else if (result.classification === "auth-failure" && result.message === "no credentials configured") {
34
+ unconfiguredProviders.push(provider);
35
+ }
36
+ else {
37
+ // Configured but ping failed (expired token, provider also down, etc.)
38
+ failingProviders.push({ provider, classification: result.classification });
39
+ }
40
+ }
41
+ const lines = [`${errorSummary}.`];
42
+ if (workingProviders.length > 0) {
43
+ const switchDescriptions = workingProviders.map((p) => {
44
+ const model = providerModels[p];
45
+ return model ? `${p} (${model})` : /* v8 ignore next -- defensive: model always present in secrets @preserve */ p;
46
+ });
47
+ const switchOptions = workingProviders.map((p) => `"switch to ${p}"`).join(" or ");
48
+ lines.push(`these providers are ready to go: ${switchDescriptions.join(", ")}.`);
49
+ lines.push(`reply ${switchOptions} to continue.`);
50
+ }
51
+ if (failingProviders.length > 0) {
52
+ for (const { provider, classification } of failingProviders) {
53
+ /* v8 ignore next -- defensive: all classifications have labels @preserve */
54
+ const detail = FAILING_PROVIDER_LABELS[classification] ?? "could not be reached";
55
+ lines.push(`${provider} is configured but ${detail}. run \`ouro auth --agent ${agentName} --provider ${provider}\` to refresh.`);
56
+ }
57
+ }
58
+ if (unconfiguredProviders.length > 0) {
59
+ lines.push(`to set up ${unconfiguredProviders.join(", ")}, run \`ouro auth --agent ${agentName}\` in terminal.`);
60
+ }
61
+ if (workingProviders.length === 0 && unconfiguredProviders.length === 0 && failingProviders.length === 0) {
62
+ lines.push(`no other providers are available. run \`ouro auth --agent ${agentName}\` in terminal to configure one.`);
63
+ }
64
+ (0, runtime_1.emitNervesEvent)({
65
+ component: "engine",
66
+ event: "engine.failover_context_built",
67
+ message: "built provider failover context",
68
+ meta: { currentProvider, classification, workingProviders, unconfiguredProviders },
69
+ });
70
+ return {
71
+ errorSummary,
72
+ classification,
73
+ currentProvider,
74
+ agentName,
75
+ workingProviders,
76
+ unconfiguredProviders,
77
+ userMessage: lines.join(" "),
78
+ };
79
+ }
80
+ function handleFailoverReply(reply, context) {
81
+ const lower = reply.toLowerCase().trim();
82
+ for (const provider of context.workingProviders) {
83
+ if (lower === `switch to ${provider}` || lower === provider) {
84
+ return { action: "switch", provider };
85
+ }
86
+ }
87
+ return { action: "dismiss" };
88
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeErrorMessage = sanitizeErrorMessage;
4
+ exports.pingProvider = pingProvider;
5
+ exports.runHealthInventory = runHealthInventory;
6
+ const anthropic_1 = require("./providers/anthropic");
7
+ const azure_1 = require("./providers/azure");
8
+ const minimax_1 = require("./providers/minimax");
9
+ const openai_codex_1 = require("./providers/openai-codex");
10
+ const github_copilot_1 = require("./providers/github-copilot");
11
+ const auth_flow_1 = require("./daemon/auth-flow");
12
+ const runtime_1 = require("../nerves/runtime");
13
+ const PING_TIMEOUT_MS = 10_000;
14
+ /**
15
+ * Strip raw JSON API response bodies from error messages.
16
+ * SDK errors often include the full response body: "400 {"type":"error","error":...}".
17
+ * Extract just the HTTP status prefix or a short summary.
18
+ */
19
+ function sanitizeErrorMessage(message) {
20
+ // Match "NNN {json...}" pattern — keep the status code, drop the JSON
21
+ const match = message.match(/^(\d{3})\s*\{/);
22
+ if (match) {
23
+ // Try to extract the inner message from the JSON
24
+ try {
25
+ const json = JSON.parse(message.slice(match[1].length).trim());
26
+ const inner = json?.error?.message;
27
+ if (typeof inner === "string" && inner && inner !== "Error") {
28
+ return `${match[1]} ${inner}`;
29
+ }
30
+ }
31
+ catch { /* not valid JSON, fall through */ }
32
+ return `HTTP ${match[1]}`;
33
+ }
34
+ return message;
35
+ }
36
+ function hasEmptyCredentials(provider, config) {
37
+ switch (provider) {
38
+ case "anthropic":
39
+ return !config.setupToken;
40
+ case "openai-codex":
41
+ return !config.oauthAccessToken;
42
+ case "minimax":
43
+ return !config.apiKey;
44
+ case "azure": {
45
+ const azure = config;
46
+ return !(azure.apiKey && azure.endpoint && azure.deployment);
47
+ }
48
+ case "github-copilot": {
49
+ const copilot = config;
50
+ return !(copilot.githubToken && copilot.baseUrl);
51
+ }
52
+ /* v8 ignore next 2 -- exhaustive: all providers handled above @preserve */
53
+ default:
54
+ return true;
55
+ }
56
+ }
57
+ function createRuntimeForPing(provider, config) {
58
+ switch (provider) {
59
+ case "anthropic":
60
+ return (0, anthropic_1.createAnthropicProviderRuntime)(config);
61
+ case "azure":
62
+ return (0, azure_1.createAzureProviderRuntime)(config);
63
+ case "minimax":
64
+ return (0, minimax_1.createMinimaxProviderRuntime)(config);
65
+ case "openai-codex":
66
+ return (0, openai_codex_1.createOpenAICodexProviderRuntime)(config);
67
+ case "github-copilot":
68
+ return (0, github_copilot_1.createGithubCopilotProviderRuntime)(config);
69
+ /* v8 ignore next 2 -- exhaustive: all providers handled above @preserve */
70
+ default:
71
+ throw new Error(`unsupported provider for ping: ${provider}`);
72
+ }
73
+ }
74
+ async function pingProvider(provider, config) {
75
+ if (hasEmptyCredentials(provider, config)) {
76
+ return { ok: false, classification: "auth-failure", message: "no credentials configured" };
77
+ }
78
+ let runtime;
79
+ try {
80
+ runtime = createRuntimeForPing(provider, config);
81
+ /* v8 ignore start -- factory creation failure: tested via individual provider init tests @preserve */
82
+ }
83
+ catch (error) {
84
+ return {
85
+ ok: false,
86
+ classification: "auth-failure",
87
+ message: error instanceof Error ? error.message : String(error),
88
+ };
89
+ }
90
+ /* v8 ignore stop */
91
+ try {
92
+ const controller = new AbortController();
93
+ /* v8 ignore next -- timeout callback: only fires after 10s, tests resolve faster @preserve */
94
+ const timeout = setTimeout(() => controller.abort(), PING_TIMEOUT_MS);
95
+ try {
96
+ // Minimal API call — no thinking, no reasoning, no tools.
97
+ if (provider === "anthropic") {
98
+ // Use haiku for the ping — setup tokens may not have access to newer
99
+ // models, but if haiku works, the credentials are valid.
100
+ // Override the beta header to exclude thinking (which requires a
101
+ // thinking param in the request body).
102
+ const client = runtime.client;
103
+ await client.messages.create({ model: "claude-haiku-4-5-20251001", max_tokens: 1, messages: [{ role: "user", content: "ping" }] }, { signal: controller.signal, headers: { "anthropic-beta": "claude-code-20250219,oauth-2025-04-20" } });
104
+ }
105
+ else {
106
+ // OpenAI-compatible providers (azure, codex, minimax, github-copilot)
107
+ const client = runtime.client;
108
+ await client.chat.completions.create({ model: runtime.model, max_tokens: 1, messages: [{ role: "user", content: "ping" }] }, { signal: controller.signal });
109
+ }
110
+ return { ok: true };
111
+ }
112
+ finally {
113
+ clearTimeout(timeout);
114
+ }
115
+ }
116
+ catch (error) {
117
+ const err = error instanceof Error ? error : /* v8 ignore next -- defensive @preserve */ new Error(String(error));
118
+ let classification;
119
+ try {
120
+ classification = runtime.classifyError(err);
121
+ }
122
+ catch {
123
+ /* v8 ignore next -- defensive: classifyError should not throw @preserve */
124
+ classification = "unknown";
125
+ }
126
+ (0, runtime_1.emitNervesEvent)({
127
+ component: "engine",
128
+ event: "engine.provider_ping_fail",
129
+ message: `provider ping failed: ${provider}`,
130
+ meta: { provider, classification, error: err.message },
131
+ });
132
+ return { ok: false, classification, message: sanitizeErrorMessage(err.message) };
133
+ }
134
+ }
135
+ const PINGABLE_PROVIDERS = ["anthropic", "openai-codex", "azure", "minimax", "github-copilot"];
136
+ async function runHealthInventory(agentName, currentProvider, deps = {}) {
137
+ /* v8 ignore next -- default: tests inject ping dep @preserve */
138
+ const ping = deps.ping ?? pingProvider;
139
+ const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agentName);
140
+ const providers = PINGABLE_PROVIDERS.filter((p) => p !== currentProvider);
141
+ const results = await Promise.all(providers.map(async (provider) => {
142
+ const config = secrets.providers[provider];
143
+ const result = await ping(provider, config);
144
+ return [provider, result];
145
+ }));
146
+ const inventory = {};
147
+ for (const [provider, result] of results) {
148
+ inventory[provider] = result;
149
+ }
150
+ return inventory;
151
+ }