@ouro.bot/cli 0.1.0-alpha.13 → 0.1.0-alpha.131
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/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/changelog.json +814 -0
- package/dist/heart/active-work.js +622 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +66 -21
- package/dist/heart/core.js +518 -100
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1516 -195
- package/dist/heart/daemon/daemon-entry.js +43 -2
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +261 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -72
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +57 -29
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +7 -4
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +64 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/safe-workspace.js +381 -0
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +108 -24
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +194 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +12 -0
- package/dist/mind/context.js +60 -14
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +456 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +39 -9
- package/dist/repertoire/coding/tools.js +171 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +925 -250
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +915 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +388 -83
- package/dist/senses/pipeline.js +444 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +9 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- 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.includes(`switch to ${provider}`) || lower === provider) {
|
|
84
|
+
return { action: "switch", provider };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { action: "dismiss" };
|
|
88
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
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/HTML API response bodies from error messages.
|
|
16
|
+
* SDK errors often include the full response: "400 {"type":"error",...}" or "403 <html>...".
|
|
17
|
+
* Extract just the HTTP status and a short human-readable summary.
|
|
18
|
+
*/
|
|
19
|
+
function sanitizeErrorMessage(message) {
|
|
20
|
+
const statusMatch = message.match(/^(\d{3})\s/);
|
|
21
|
+
if (!statusMatch)
|
|
22
|
+
return message;
|
|
23
|
+
const status = statusMatch[1];
|
|
24
|
+
const body = message.slice(status.length).trim();
|
|
25
|
+
// HTML response (Cloudflare challenge, error pages, etc.)
|
|
26
|
+
if (body.startsWith("<") || body.includes("<!DOCTYPE") || body.includes("<html")) {
|
|
27
|
+
return `HTTP ${status}`;
|
|
28
|
+
}
|
|
29
|
+
// JSON response
|
|
30
|
+
if (body.startsWith("{")) {
|
|
31
|
+
try {
|
|
32
|
+
const json = JSON.parse(body);
|
|
33
|
+
const inner = json?.error?.message;
|
|
34
|
+
if (typeof inner === "string" && inner && inner !== "Error") {
|
|
35
|
+
return `${status} ${inner}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch { /* not valid JSON */ }
|
|
39
|
+
return `HTTP ${status}`;
|
|
40
|
+
}
|
|
41
|
+
// Already clean (e.g., "401 Provided authentication token is expired.")
|
|
42
|
+
return message;
|
|
43
|
+
}
|
|
44
|
+
function hasEmptyCredentials(provider, config) {
|
|
45
|
+
switch (provider) {
|
|
46
|
+
case "anthropic":
|
|
47
|
+
return !config.setupToken;
|
|
48
|
+
case "openai-codex":
|
|
49
|
+
return !config.oauthAccessToken;
|
|
50
|
+
case "minimax":
|
|
51
|
+
return !config.apiKey;
|
|
52
|
+
case "azure": {
|
|
53
|
+
const azure = config;
|
|
54
|
+
return !(azure.apiKey && azure.endpoint && azure.deployment);
|
|
55
|
+
}
|
|
56
|
+
case "github-copilot": {
|
|
57
|
+
const copilot = config;
|
|
58
|
+
return !(copilot.githubToken && copilot.baseUrl);
|
|
59
|
+
}
|
|
60
|
+
/* v8 ignore next 2 -- exhaustive: all providers handled above @preserve */
|
|
61
|
+
default:
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function createRuntimeForPing(provider, config) {
|
|
66
|
+
switch (provider) {
|
|
67
|
+
case "anthropic":
|
|
68
|
+
return (0, anthropic_1.createAnthropicProviderRuntime)(config);
|
|
69
|
+
case "azure":
|
|
70
|
+
return (0, azure_1.createAzureProviderRuntime)(config);
|
|
71
|
+
case "minimax":
|
|
72
|
+
return (0, minimax_1.createMinimaxProviderRuntime)(config);
|
|
73
|
+
case "openai-codex":
|
|
74
|
+
return (0, openai_codex_1.createOpenAICodexProviderRuntime)(config);
|
|
75
|
+
case "github-copilot":
|
|
76
|
+
return (0, github_copilot_1.createGithubCopilotProviderRuntime)(config);
|
|
77
|
+
/* v8 ignore next 2 -- exhaustive: all providers handled above @preserve */
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`unsupported provider for ping: ${provider}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function pingProvider(provider, config) {
|
|
83
|
+
if (hasEmptyCredentials(provider, config)) {
|
|
84
|
+
return { ok: false, classification: "auth-failure", message: "no credentials configured" };
|
|
85
|
+
}
|
|
86
|
+
let runtime;
|
|
87
|
+
try {
|
|
88
|
+
runtime = createRuntimeForPing(provider, config);
|
|
89
|
+
/* v8 ignore start -- factory creation failure: tested via individual provider init tests @preserve */
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
classification: "auth-failure",
|
|
95
|
+
message: error instanceof Error ? error.message : String(error),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/* v8 ignore stop */
|
|
99
|
+
try {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
/* v8 ignore next -- timeout callback: only fires after 10s, tests resolve faster @preserve */
|
|
102
|
+
const timeout = setTimeout(() => controller.abort(), PING_TIMEOUT_MS);
|
|
103
|
+
try {
|
|
104
|
+
// Minimal API call — no thinking, no reasoning, no tools.
|
|
105
|
+
if (provider === "anthropic") {
|
|
106
|
+
// Use haiku for the ping — setup tokens may not have access to newer
|
|
107
|
+
// models, but if haiku works, the credentials are valid.
|
|
108
|
+
// Override the beta header to exclude thinking (which requires a
|
|
109
|
+
// thinking param in the request body).
|
|
110
|
+
const client = runtime.client;
|
|
111
|
+
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" } });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// OpenAI-compatible providers (azure, codex, minimax, github-copilot)
|
|
115
|
+
const client = runtime.client;
|
|
116
|
+
await client.chat.completions.create({ model: runtime.model, max_tokens: 1, messages: [{ role: "user", content: "ping" }] }, { signal: controller.signal });
|
|
117
|
+
}
|
|
118
|
+
return { ok: true };
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const err = error instanceof Error ? error : /* v8 ignore next -- defensive @preserve */ new Error(String(error));
|
|
126
|
+
let classification;
|
|
127
|
+
try {
|
|
128
|
+
classification = runtime.classifyError(err);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
132
|
+
classification = "unknown";
|
|
133
|
+
}
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
component: "engine",
|
|
136
|
+
event: "engine.provider_ping_fail",
|
|
137
|
+
message: `provider ping failed: ${provider}`,
|
|
138
|
+
meta: { provider, classification, error: err.message },
|
|
139
|
+
});
|
|
140
|
+
return { ok: false, classification, message: sanitizeErrorMessage(err.message) };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const PINGABLE_PROVIDERS = ["anthropic", "openai-codex", "azure", "minimax", "github-copilot"];
|
|
144
|
+
async function runHealthInventory(agentName, currentProvider, deps = {}) {
|
|
145
|
+
/* v8 ignore next -- default: tests inject ping dep @preserve */
|
|
146
|
+
const ping = deps.ping ?? pingProvider;
|
|
147
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agentName);
|
|
148
|
+
const providers = PINGABLE_PROVIDERS.filter((p) => p !== currentProvider);
|
|
149
|
+
const results = await Promise.all(providers.map(async (provider) => {
|
|
150
|
+
const config = secrets.providers[provider];
|
|
151
|
+
const result = await ping(provider, config);
|
|
152
|
+
return [provider, result];
|
|
153
|
+
}));
|
|
154
|
+
const inventory = {};
|
|
155
|
+
for (const [provider, result] of results) {
|
|
156
|
+
inventory[provider] = result;
|
|
157
|
+
}
|
|
158
|
+
return inventory;
|
|
159
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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.needsRefresh = needsRefresh;
|
|
37
|
+
exports.refreshAnthropicToken = refreshAnthropicToken;
|
|
38
|
+
exports.persistTokenState = persistTokenState;
|
|
39
|
+
exports.ensureFreshToken = ensureFreshToken;
|
|
40
|
+
/* v8 ignore start -- OAuth token lifecycle: requires live API calls, tested via integration @preserve */
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
const identity_1 = require("../identity");
|
|
44
|
+
const OAUTH_TOKEN_ENDPOINT = "https://console.anthropic.com/v1/oauth/token";
|
|
45
|
+
const OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
46
|
+
const REFRESH_MARGIN_MS = 5 * 60 * 1000; // refresh 5 minutes before expiry
|
|
47
|
+
/**
|
|
48
|
+
* Check if the Anthropic OAuth token needs refreshing.
|
|
49
|
+
* Returns true if no expiresAt is set (legacy token) or if within 5 min of expiry.
|
|
50
|
+
*/
|
|
51
|
+
function needsRefresh(expiresAt) {
|
|
52
|
+
if (!expiresAt)
|
|
53
|
+
return true; // legacy token with no expiry — always try refresh
|
|
54
|
+
return Date.now() > expiresAt - REFRESH_MARGIN_MS;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Refresh an Anthropic OAuth access token using the refresh token.
|
|
58
|
+
* Returns the new token state or null if refresh fails.
|
|
59
|
+
*/
|
|
60
|
+
async function refreshAnthropicToken(refreshToken, fetchImpl = fetch) {
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetchImpl(OAUTH_TOKEN_ENDPOINT, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
grant_type: "refresh_token",
|
|
67
|
+
refresh_token: refreshToken,
|
|
68
|
+
client_id: OAUTH_CLIENT_ID,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
(0, runtime_1.emitNervesEvent)({
|
|
73
|
+
level: "warn",
|
|
74
|
+
component: "engine",
|
|
75
|
+
event: "engine.anthropic_token_refresh_failed",
|
|
76
|
+
message: `token refresh failed: ${response.status}`,
|
|
77
|
+
meta: { status: response.status },
|
|
78
|
+
});
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const json = await response.json();
|
|
82
|
+
if (!json.access_token) {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
level: "warn",
|
|
85
|
+
component: "engine",
|
|
86
|
+
event: "engine.anthropic_token_refresh_failed",
|
|
87
|
+
message: "token refresh returned no access_token",
|
|
88
|
+
meta: {},
|
|
89
|
+
});
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const state = {
|
|
93
|
+
accessToken: json.access_token,
|
|
94
|
+
refreshToken: json.refresh_token ?? refreshToken, // keep old if not returned
|
|
95
|
+
expiresAt: Date.now() + (json.expires_in ?? 28800) * 1000, // default 8h
|
|
96
|
+
};
|
|
97
|
+
(0, runtime_1.emitNervesEvent)({
|
|
98
|
+
component: "engine",
|
|
99
|
+
event: "engine.anthropic_token_refreshed",
|
|
100
|
+
message: "anthropic OAuth token refreshed",
|
|
101
|
+
meta: { expiresAt: new Date(state.expiresAt).toISOString() },
|
|
102
|
+
});
|
|
103
|
+
return state;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
(0, runtime_1.emitNervesEvent)({
|
|
107
|
+
level: "warn",
|
|
108
|
+
component: "engine",
|
|
109
|
+
event: "engine.anthropic_token_refresh_error",
|
|
110
|
+
message: "token refresh threw",
|
|
111
|
+
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
112
|
+
});
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Persist refreshed token state back to secrets.json.
|
|
118
|
+
*/
|
|
119
|
+
function persistTokenState(agentName, state) {
|
|
120
|
+
try {
|
|
121
|
+
const secretsPath = (0, identity_1.getAgentSecretsPath)(agentName);
|
|
122
|
+
const raw = fs.readFileSync(secretsPath, "utf-8");
|
|
123
|
+
const secrets = JSON.parse(raw);
|
|
124
|
+
secrets.providers = secrets.providers ?? {};
|
|
125
|
+
secrets.providers.anthropic = secrets.providers.anthropic ?? {};
|
|
126
|
+
secrets.providers.anthropic.setupToken = state.accessToken;
|
|
127
|
+
secrets.providers.anthropic.refreshToken = state.refreshToken;
|
|
128
|
+
secrets.providers.anthropic.expiresAt = state.expiresAt;
|
|
129
|
+
fs.writeFileSync(secretsPath, JSON.stringify(secrets, null, 2) + "\n", "utf-8");
|
|
130
|
+
/* v8 ignore start -- defensive: persistence failure must not crash the provider @preserve */
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
(0, runtime_1.emitNervesEvent)({
|
|
134
|
+
level: "warn",
|
|
135
|
+
component: "engine",
|
|
136
|
+
event: "engine.anthropic_token_persist_error",
|
|
137
|
+
message: "failed to persist refreshed token",
|
|
138
|
+
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/* v8 ignore stop */
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Ensure the Anthropic token is fresh. If expired, refresh and persist.
|
|
145
|
+
* Returns the current valid access token, or null if refresh failed and
|
|
146
|
+
* the existing token is expired.
|
|
147
|
+
*/
|
|
148
|
+
async function ensureFreshToken(currentToken, refreshToken, expiresAt, agentName, fetchImpl) {
|
|
149
|
+
if (!needsRefresh(expiresAt)) {
|
|
150
|
+
return currentToken; // still fresh
|
|
151
|
+
}
|
|
152
|
+
if (!refreshToken) {
|
|
153
|
+
// No refresh token — use the current token as-is (may be expired)
|
|
154
|
+
return currentToken;
|
|
155
|
+
}
|
|
156
|
+
const newState = await refreshAnthropicToken(refreshToken, fetchImpl);
|
|
157
|
+
if (!newState) {
|
|
158
|
+
return currentToken; // refresh failed — try the old token
|
|
159
|
+
}
|
|
160
|
+
persistTokenState(agentName, newState);
|
|
161
|
+
return newState.accessToken;
|
|
162
|
+
}
|
|
163
|
+
/* v8 ignore stop */
|