@ouro.bot/cli 0.1.0-alpha.235 → 0.1.0-alpha.237
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +9 -0
- package/dist/heart/config-registry.js +26 -27
- package/dist/heart/core.js +41 -36
- package/dist/heart/daemon/cli-render.js +129 -46
- package/dist/heart/daemon/daemon.js +4 -0
- package/dist/heart/daemon/socket-client.js +1 -0
- package/dist/repertoire/ado-semantic.js +0 -5
- package/dist/repertoire/tools-config.js +6 -87
- package/dist/repertoire/tools-github.js +0 -1
- package/dist/repertoire/tools-teams.js +0 -3
- package/dist/repertoire/tools.js +1 -15
- package/dist/senses/inner-dialog.js +0 -1
- package/dist/senses/teams.js +0 -48
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
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.237",
|
|
6
|
+
"changes": [
|
|
7
|
+
"feat: pretty `ouro status` \u2014 ANSI colored output with box-drawing header, status dots, grouped senses/workers by agent. Added git sync info to overview.",
|
|
8
|
+
"fix: socket half-close \u2014 sendDaemonCommand now calls client.end() after writing, preventing intermittent connection hangs.",
|
|
9
|
+
"refactor: config tiers \u2014 replaced numeric T1/T2/T3 with `self` (agent-configurable) and `managed` (harness-only). All config keys are now agent-writable except `version` and `enabled`. mcpServers promoted to self.",
|
|
10
|
+
"refactor: removed confirmation system \u2014 deleted propose_config tool, confirmationRequired/confirmationAlwaysRequired/onConfirmAction from core, all tool definitions, and Teams sense. Was only wired up on Teams, silently failed everywhere else. -1,361 lines."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
4
13
|
{
|
|
5
14
|
"version": "0.1.0-alpha.235",
|
|
6
15
|
"changes": [
|
|
@@ -82,11 +82,11 @@ function validateObject(requiredFields) {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
const registryData = [
|
|
85
|
-
// ---
|
|
85
|
+
// --- Managed: harness-only ---
|
|
86
86
|
{
|
|
87
87
|
path: "version",
|
|
88
|
-
tier:
|
|
89
|
-
description: "Agent config schema version. Must be an integer >= 1.",
|
|
88
|
+
tier: "managed",
|
|
89
|
+
description: "Agent config schema version. Managed by the harness. Must be an integer >= 1.",
|
|
90
90
|
default: 2,
|
|
91
91
|
effects: "Controls which config migrations apply on load. Changing incorrectly can corrupt config.",
|
|
92
92
|
topics: ["schema", "migration"],
|
|
@@ -94,25 +94,25 @@ const registryData = [
|
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
path: "enabled",
|
|
97
|
-
tier:
|
|
98
|
-
description: "Whether the agent is enabled. When false, the agent refuses to start.",
|
|
97
|
+
tier: "managed",
|
|
98
|
+
description: "Whether the agent is enabled. Managed by the harness. When false, the agent refuses to start.",
|
|
99
99
|
default: true,
|
|
100
100
|
effects: "Disables all agent functionality when set to false.",
|
|
101
101
|
topics: ["lifecycle", "activation"],
|
|
102
102
|
validate: validateBoolean,
|
|
103
103
|
},
|
|
104
|
+
// --- Self: agent-configurable ---
|
|
104
105
|
{
|
|
105
106
|
path: "mcpServers",
|
|
106
|
-
tier:
|
|
107
|
-
description: "MCP server configurations. Maps server name to command, args, and env.",
|
|
107
|
+
tier: "self",
|
|
108
|
+
description: "MCP server configurations. Maps server name to command, args, and env. The agent can add or remove MCP servers.",
|
|
108
109
|
default: undefined,
|
|
109
|
-
effects: "Adds or removes MCP tool servers.
|
|
110
|
+
effects: "Adds or removes MCP tool servers.",
|
|
110
111
|
topics: ["tools", "mcp", "servers", "extensions"],
|
|
111
112
|
},
|
|
112
|
-
// --- Tier 2: Proposal ---
|
|
113
113
|
{
|
|
114
114
|
path: "humanFacing.provider",
|
|
115
|
-
tier:
|
|
115
|
+
tier: "self",
|
|
116
116
|
description: "Provider for human-facing interactions (CLI, Teams, BlueBubbles).",
|
|
117
117
|
default: "anthropic",
|
|
118
118
|
effects: "Changes the LLM provider used for all human-facing conversations. Affects quality, latency, and cost.",
|
|
@@ -121,7 +121,7 @@ const registryData = [
|
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
path: "humanFacing.model",
|
|
124
|
-
tier:
|
|
124
|
+
tier: "self",
|
|
125
125
|
description: "Model name for human-facing interactions.",
|
|
126
126
|
default: "claude-opus-4-6",
|
|
127
127
|
effects: "Changes the specific model used for human-facing conversations.",
|
|
@@ -130,7 +130,7 @@ const registryData = [
|
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
path: "agentFacing.provider",
|
|
133
|
-
tier:
|
|
133
|
+
tier: "self",
|
|
134
134
|
description: "Provider for agent-facing interactions (inner dialog, delegation).",
|
|
135
135
|
default: "anthropic",
|
|
136
136
|
effects: "Changes the LLM provider used for inner dialog and agent-to-agent communication.",
|
|
@@ -139,7 +139,7 @@ const registryData = [
|
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
path: "agentFacing.model",
|
|
142
|
-
tier:
|
|
142
|
+
tier: "self",
|
|
143
143
|
description: "Model name for agent-facing interactions.",
|
|
144
144
|
default: "claude-opus-4-6",
|
|
145
145
|
effects: "Changes the specific model used for inner dialog and agent-to-agent communication.",
|
|
@@ -148,7 +148,7 @@ const registryData = [
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
path: "context.maxTokens",
|
|
151
|
-
tier:
|
|
151
|
+
tier: "self",
|
|
152
152
|
description: "Maximum context window size in tokens.",
|
|
153
153
|
default: 80000,
|
|
154
154
|
effects: "Larger values allow more context but increase cost and latency. Must match model capability.",
|
|
@@ -157,7 +157,7 @@ const registryData = [
|
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
path: "senses.cli",
|
|
160
|
-
tier:
|
|
160
|
+
tier: "self",
|
|
161
161
|
description: "CLI sense configuration. Controls whether the CLI interface is enabled.",
|
|
162
162
|
default: { enabled: true },
|
|
163
163
|
effects: "Enables or disables the CLI (terminal) interaction channel.",
|
|
@@ -166,7 +166,7 @@ const registryData = [
|
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
path: "senses.teams",
|
|
169
|
-
tier:
|
|
169
|
+
tier: "self",
|
|
170
170
|
description: "Teams sense configuration. Controls whether the Teams interface is enabled.",
|
|
171
171
|
default: { enabled: false },
|
|
172
172
|
effects: "Enables or disables the Microsoft Teams interaction channel.",
|
|
@@ -175,7 +175,7 @@ const registryData = [
|
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
path: "senses.bluebubbles",
|
|
178
|
-
tier:
|
|
178
|
+
tier: "self",
|
|
179
179
|
description: "BlueBubbles sense configuration. Controls whether the iMessage interface is enabled.",
|
|
180
180
|
default: { enabled: false },
|
|
181
181
|
effects: "Enables or disables the BlueBubbles (iMessage) interaction channel.",
|
|
@@ -184,7 +184,7 @@ const registryData = [
|
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
path: "sync.enabled",
|
|
187
|
-
tier:
|
|
187
|
+
tier: "self",
|
|
188
188
|
description: "Whether git-based bundle sync is enabled.",
|
|
189
189
|
default: false,
|
|
190
190
|
effects: "Enables automatic synchronization of agent state via git. Requires sync.remote to be configured.",
|
|
@@ -193,17 +193,16 @@ const registryData = [
|
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
195
|
path: "sync.remote",
|
|
196
|
-
tier:
|
|
196
|
+
tier: "self",
|
|
197
197
|
description: "Git remote name used for bundle sync.",
|
|
198
198
|
default: "origin",
|
|
199
199
|
effects: "Controls which git remote is used when sync is enabled.",
|
|
200
200
|
topics: ["sync", "git", "remote"],
|
|
201
201
|
validate: validateString,
|
|
202
202
|
},
|
|
203
|
-
// --- Tier 1: Self-service ---
|
|
204
203
|
{
|
|
205
204
|
path: "context.contextMargin",
|
|
206
|
-
tier:
|
|
205
|
+
tier: "self",
|
|
207
206
|
description: "Percentage of context window reserved as margin before compaction triggers.",
|
|
208
207
|
default: 20,
|
|
209
208
|
effects: "Higher values trigger compaction earlier, preserving more headroom. Lower values use more context.",
|
|
@@ -212,7 +211,7 @@ const registryData = [
|
|
|
212
211
|
},
|
|
213
212
|
{
|
|
214
213
|
path: "phrases.thinking",
|
|
215
|
-
tier:
|
|
214
|
+
tier: "self",
|
|
216
215
|
description: "Array of phrases displayed while the agent is thinking.",
|
|
217
216
|
default: ["working"],
|
|
218
217
|
effects: "Changes the thinking indicator text shown to users. Purely cosmetic.",
|
|
@@ -221,7 +220,7 @@ const registryData = [
|
|
|
221
220
|
},
|
|
222
221
|
{
|
|
223
222
|
path: "phrases.tool",
|
|
224
|
-
tier:
|
|
223
|
+
tier: "self",
|
|
225
224
|
description: "Array of phrases displayed while the agent is running a tool.",
|
|
226
225
|
default: ["running tool"],
|
|
227
226
|
effects: "Changes the tool-use indicator text shown to users. Purely cosmetic.",
|
|
@@ -230,7 +229,7 @@ const registryData = [
|
|
|
230
229
|
},
|
|
231
230
|
{
|
|
232
231
|
path: "phrases.followup",
|
|
233
|
-
tier:
|
|
232
|
+
tier: "self",
|
|
234
233
|
description: "Array of phrases displayed during follow-up processing.",
|
|
235
234
|
default: ["processing"],
|
|
236
235
|
effects: "Changes the follow-up indicator text shown to users. Purely cosmetic.",
|
|
@@ -239,7 +238,7 @@ const registryData = [
|
|
|
239
238
|
},
|
|
240
239
|
{
|
|
241
240
|
path: "shell.defaultTimeout",
|
|
242
|
-
tier:
|
|
241
|
+
tier: "self",
|
|
243
242
|
description: "Default timeout in milliseconds for shell command execution.",
|
|
244
243
|
default: undefined,
|
|
245
244
|
effects: "Controls how long shell commands run before timing out. Undefined uses system default.",
|
|
@@ -248,7 +247,7 @@ const registryData = [
|
|
|
248
247
|
},
|
|
249
248
|
{
|
|
250
249
|
path: "logging.level",
|
|
251
|
-
tier:
|
|
250
|
+
tier: "self",
|
|
252
251
|
description: "Minimum log level: debug, info, warn, or error.",
|
|
253
252
|
default: undefined,
|
|
254
253
|
effects: "Controls verbosity of runtime logging. Lower levels produce more output.",
|
|
@@ -257,7 +256,7 @@ const registryData = [
|
|
|
257
256
|
},
|
|
258
257
|
{
|
|
259
258
|
path: "logging.sinks",
|
|
260
|
-
tier:
|
|
259
|
+
tier: "self",
|
|
261
260
|
description: "Array of log sink types: 'terminal' and/or 'ndjson'.",
|
|
262
261
|
default: undefined,
|
|
263
262
|
effects: "Controls where log output is directed. Terminal shows in console, ndjson writes structured logs.",
|
package/dist/heart/core.js
CHANGED
|
@@ -905,23 +905,6 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
905
905
|
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
906
906
|
continue;
|
|
907
907
|
}
|
|
908
|
-
// Confirmation check for mutate tools.
|
|
909
|
-
// confirmationAlwaysRequired tools cannot be bypassed by skipConfirmation.
|
|
910
|
-
const needsConfirmation = (0, tools_1.isConfirmationRequired)(tc.name) && (!options?.skipConfirmation || (0, tools_1.isConfirmationAlwaysRequired)(tc.name));
|
|
911
|
-
if (needsConfirmation) {
|
|
912
|
-
let decision = "denied";
|
|
913
|
-
if (callbacks.onConfirmAction) {
|
|
914
|
-
decision = await callbacks.onConfirmAction(tc.name, args);
|
|
915
|
-
}
|
|
916
|
-
if (decision !== "confirmed") {
|
|
917
|
-
const cancelled = "Action cancelled by user.";
|
|
918
|
-
callbacks.onToolStart(tc.name, args);
|
|
919
|
-
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
920
|
-
messages.push({ role: "tool", tool_call_id: tc.id, content: cancelled });
|
|
921
|
-
providerRuntime.appendToolOutput(tc.id, cancelled);
|
|
922
|
-
continue;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
908
|
callbacks.onToolStart(tc.name, args);
|
|
926
909
|
let toolResult;
|
|
927
910
|
let success;
|
|
@@ -959,20 +942,48 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
959
942
|
callbacks.onError(new Error("context trimmed, retrying..."), "transient");
|
|
960
943
|
continue;
|
|
961
944
|
}
|
|
962
|
-
// Transient errors: retry with exponential backoff
|
|
963
|
-
|
|
945
|
+
// Transient errors: retry with exponential backoff.
|
|
946
|
+
// Two-layer detection: generic isTransientError (HTTP codes, network patterns)
|
|
947
|
+
// PLUS provider-specific classifyError (catches SDK-wrapped errors that
|
|
948
|
+
// generic detection might miss).
|
|
949
|
+
const errorForClassification = e instanceof Error ? e : /* v8 ignore next -- defensive @preserve */ new Error(String(e));
|
|
950
|
+
let providerClassification;
|
|
951
|
+
try {
|
|
952
|
+
providerClassification = providerRuntime.classifyError(errorForClassification);
|
|
953
|
+
}
|
|
954
|
+
catch {
|
|
955
|
+
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
956
|
+
providerClassification = "unknown";
|
|
957
|
+
}
|
|
958
|
+
const retryableByClassification = providerClassification === "rate-limit"
|
|
959
|
+
|| providerClassification === "server-error"
|
|
960
|
+
|| providerClassification === "network-error";
|
|
961
|
+
const retryableByGeneric = isTransientError(e);
|
|
962
|
+
const shouldRetry = (retryableByGeneric || retryableByClassification) && retryCount < MAX_RETRIES;
|
|
963
|
+
(0, runtime_1.emitNervesEvent)({
|
|
964
|
+
level: shouldRetry ? "info" : "warn",
|
|
965
|
+
event: shouldRetry ? "engine.provider_retry" : "engine.provider_retry_skip",
|
|
966
|
+
component: "engine",
|
|
967
|
+
message: shouldRetry
|
|
968
|
+
? `provider error is retryable (attempt ${retryCount + 1}/${MAX_RETRIES})`
|
|
969
|
+
: `provider error is not retryable or retries exhausted`,
|
|
970
|
+
meta: {
|
|
971
|
+
provider: providerRuntime.id,
|
|
972
|
+
model: providerRuntime.model,
|
|
973
|
+
retryCount,
|
|
974
|
+
maxRetries: MAX_RETRIES,
|
|
975
|
+
retryableByGeneric,
|
|
976
|
+
retryableByClassification,
|
|
977
|
+
providerClassification,
|
|
978
|
+
errorMessage: errorForClassification.message.slice(0, 200),
|
|
979
|
+
httpStatus: e.status ?? null,
|
|
980
|
+
},
|
|
981
|
+
});
|
|
982
|
+
if (shouldRetry) {
|
|
964
983
|
retryCount++;
|
|
965
984
|
const delay = RETRY_BASE_MS * Math.pow(2, retryCount - 1);
|
|
966
|
-
let classification;
|
|
967
|
-
try {
|
|
968
|
-
classification = providerRuntime.classifyError(e instanceof Error ? e : /* v8 ignore next -- defensive: errors are always Error instances @preserve */ new Error(String(e)));
|
|
969
|
-
}
|
|
970
|
-
catch {
|
|
971
|
-
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
972
|
-
classification = classifyTransientError(e);
|
|
973
|
-
}
|
|
974
985
|
/* v8 ignore next -- defensive: all classifications have labels @preserve */
|
|
975
|
-
const cause = TRANSIENT_RETRY_LABELS[
|
|
986
|
+
const cause = TRANSIENT_RETRY_LABELS[providerClassification] ?? providerClassification;
|
|
976
987
|
callbacks.onError(new Error(`${cause}, retrying in ${delay / 1000}s (${retryCount}/${MAX_RETRIES})...`), "transient");
|
|
977
988
|
// Wait with abort support
|
|
978
989
|
const aborted = await new Promise((resolve) => {
|
|
@@ -995,14 +1006,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
995
1006
|
providerRuntime.resetTurnState(messages);
|
|
996
1007
|
continue;
|
|
997
1008
|
}
|
|
998
|
-
terminalError =
|
|
999
|
-
|
|
1000
|
-
terminalErrorClassification = providerRuntime.classifyError(terminalError);
|
|
1001
|
-
}
|
|
1002
|
-
catch {
|
|
1003
|
-
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
1004
|
-
terminalErrorClassification = "unknown";
|
|
1005
|
-
}
|
|
1009
|
+
terminalError = errorForClassification;
|
|
1010
|
+
terminalErrorClassification = providerClassification;
|
|
1006
1011
|
/* v8 ignore start — auth-failure guidance: tested via provider error classification tests @preserve */
|
|
1007
1012
|
if (terminalErrorClassification === "auth-failure") {
|
|
1008
1013
|
const agentName = (0, identity_2.getAgentName)();
|
|
@@ -89,6 +89,8 @@ function parseStatusPayload(data) {
|
|
|
89
89
|
senseCount: numberField(overview.senseCount) ?? 0,
|
|
90
90
|
entryPath: stringField(overview.entryPath) ?? "unknown",
|
|
91
91
|
mode: stringField(overview.mode) ?? "unknown",
|
|
92
|
+
syncEnabled: booleanField(overview.syncEnabled) ?? false,
|
|
93
|
+
syncRemote: stringField(overview.syncRemote) ?? "origin",
|
|
92
94
|
};
|
|
93
95
|
const parsedSenses = senses.map((entry) => {
|
|
94
96
|
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
@@ -141,6 +143,44 @@ function parseStatusPayload(data) {
|
|
|
141
143
|
workers: parsedWorkers,
|
|
142
144
|
};
|
|
143
145
|
}
|
|
146
|
+
// ── ANSI color helpers (private) ──
|
|
147
|
+
const RESET = "\x1b[0m";
|
|
148
|
+
const BOLD = "\x1b[1m";
|
|
149
|
+
const DIM = "\x1b[2m";
|
|
150
|
+
const TEAL = "\x1b[38;2;78;201;176m";
|
|
151
|
+
const GREEN = "\x1b[38;2;46;204;64m";
|
|
152
|
+
const RED = "\x1b[38;2;231;76;60m";
|
|
153
|
+
const YELLOW = "\x1b[38;2;230;190;50m";
|
|
154
|
+
/* v8 ignore start -- cosmetic ANSI wrappers @preserve */
|
|
155
|
+
function teal(text) { return `${TEAL}${text}${RESET}`; }
|
|
156
|
+
function green(text) { return `${GREEN}${text}${RESET}`; }
|
|
157
|
+
function red(text) { return `${RED}${text}${RESET}`; }
|
|
158
|
+
function yellow(text) { return `${YELLOW}${text}${RESET}`; }
|
|
159
|
+
function bold(text) { return `${BOLD}${text}${RESET}`; }
|
|
160
|
+
function dim(text) { return `${DIM}${text}${RESET}`; }
|
|
161
|
+
/* v8 ignore stop */
|
|
162
|
+
/* v8 ignore start -- cosmetic display: status dot color mapping tested visually @preserve */
|
|
163
|
+
function statusDot(status) {
|
|
164
|
+
switch (status) {
|
|
165
|
+
case "running":
|
|
166
|
+
case "ok":
|
|
167
|
+
case "interactive":
|
|
168
|
+
case "enabled":
|
|
169
|
+
return green("●");
|
|
170
|
+
case "crashed":
|
|
171
|
+
case "warn":
|
|
172
|
+
case "error":
|
|
173
|
+
return red("●");
|
|
174
|
+
case "needs_config":
|
|
175
|
+
return yellow("●");
|
|
176
|
+
case "disabled":
|
|
177
|
+
case "stopped":
|
|
178
|
+
return dim("○");
|
|
179
|
+
default:
|
|
180
|
+
return dim("●");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/* v8 ignore stop */
|
|
144
184
|
// ── Formatters ──
|
|
145
185
|
function humanizeSenseName(sense, label) {
|
|
146
186
|
if (label)
|
|
@@ -153,6 +193,7 @@ function humanizeSenseName(sense, label) {
|
|
|
153
193
|
return "Teams";
|
|
154
194
|
return sense;
|
|
155
195
|
}
|
|
196
|
+
/* v8 ignore start -- utility formatter; retained for non-status table output @preserve */
|
|
156
197
|
function formatTable(headers, rows) {
|
|
157
198
|
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
|
|
158
199
|
const renderRow = (row) => `| ${row.map((cell, index) => (index === row.length - 1
|
|
@@ -165,56 +206,96 @@ function formatTable(headers, rows) {
|
|
|
165
206
|
...rows.map(renderRow),
|
|
166
207
|
].join("\n");
|
|
167
208
|
}
|
|
209
|
+
/* v8 ignore stop */
|
|
168
210
|
function formatDaemonStatusOutput(response, fallback) {
|
|
169
211
|
const payload = parseStatusPayload(response.data);
|
|
170
212
|
if (!payload)
|
|
171
213
|
return fallback;
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
214
|
+
const ov = payload.overview;
|
|
215
|
+
const lines = [];
|
|
216
|
+
// ── Header banner ──
|
|
217
|
+
const modeTag = ov.mode === "dev" ? "dev" : "";
|
|
218
|
+
const daemonStatus = ov.daemon === "running" ? green("●") + " " + bold("running") : red("●") + " " + bold(ov.daemon);
|
|
219
|
+
const modeStr = modeTag ? ` ${dim(`(${modeTag})`)}` : "";
|
|
220
|
+
const bannerContent = ` ${bold(ov.version)} ${daemonStatus}${modeStr} `;
|
|
221
|
+
// Measure raw content width (strip ANSI for width calc)
|
|
222
|
+
const rawBanner = bannerContent.replace(/\x1b\[[0-9;]*m/g, "");
|
|
223
|
+
const bannerWidth = Math.max(rawBanner.length, 42);
|
|
224
|
+
const titleLabel = " ouroboros daemon ";
|
|
225
|
+
const topRightPad = Math.max(0, bannerWidth - titleLabel.length - 2);
|
|
226
|
+
lines.push(` ${teal("╭─")}${teal(titleLabel)}${teal("─".repeat(topRightPad))}${teal("╮")}`);
|
|
227
|
+
lines.push(` ${teal("│")}${bannerContent}${" ".repeat(Math.max(0, bannerWidth - rawBanner.length))}${teal("│")}`);
|
|
228
|
+
lines.push(` ${teal("╰")}${teal("─".repeat(bannerWidth))}${teal("╯")}`);
|
|
229
|
+
lines.push("");
|
|
230
|
+
// ── Key-value overview ──
|
|
231
|
+
const kvLine = (label, value) => ` ${teal(label.padEnd(11))} ${value}`;
|
|
232
|
+
lines.push(kvLine("Socket", ov.socketPath));
|
|
233
|
+
lines.push(kvLine("Outlook", ov.outlookUrl));
|
|
234
|
+
lines.push(kvLine("Health", `${statusDot(ov.health)} ${ov.health}`));
|
|
235
|
+
/* v8 ignore next -- cosmetic: sync-enabled branch requires agent with sync configured @preserve */
|
|
236
|
+
const syncLabel = ov.syncEnabled ? `${green("●")} enabled (remote: ${ov.syncRemote})` : `${dim("○")} disabled`;
|
|
237
|
+
lines.push(kvLine("Git Sync", syncLabel));
|
|
238
|
+
lines.push(kvLine("Updated", ov.lastUpdated));
|
|
239
|
+
lines.push("");
|
|
240
|
+
// ── Senses ──
|
|
241
|
+
if (payload.senses.length > 0) {
|
|
242
|
+
lines.push(` ${teal("──")} ${bold("Senses")} ${teal("─".repeat(37))}`);
|
|
243
|
+
// Group senses by agent
|
|
244
|
+
const sensesByAgent = new Map();
|
|
245
|
+
for (const row of payload.senses) {
|
|
246
|
+
const list = sensesByAgent.get(row.agent) ?? [];
|
|
247
|
+
list.push(row);
|
|
248
|
+
sensesByAgent.set(row.agent, list);
|
|
249
|
+
}
|
|
250
|
+
// Calculate column widths for alignment
|
|
251
|
+
const allSenseNames = payload.senses.map((r) => humanizeSenseName(r.sense, r.label));
|
|
252
|
+
const nameWidth = Math.max(12, ...allSenseNames.map((n) => n.length));
|
|
253
|
+
const allStatuses = payload.senses.map((r) => r.status);
|
|
254
|
+
const statusWidth = Math.max(10, ...allStatuses.map((s) => s.length));
|
|
255
|
+
for (const [agent, rows] of sensesByAgent) {
|
|
256
|
+
lines.push(` ${bold(agent)}`);
|
|
257
|
+
for (const row of rows) {
|
|
258
|
+
const name = humanizeSenseName(row.sense, row.label).padEnd(nameWidth);
|
|
259
|
+
const dot = row.enabled ? statusDot(row.status) : dim("○");
|
|
260
|
+
const statusText = (row.enabled ? row.status : "disabled").padEnd(statusWidth);
|
|
261
|
+
lines.push(` ${name} ${dot} ${statusText} ${dim(row.detail)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
lines.push("");
|
|
265
|
+
}
|
|
266
|
+
// ── Workers ──
|
|
267
|
+
if (payload.workers.length > 0) {
|
|
268
|
+
lines.push(` ${teal("──")} ${bold("Workers")} ${teal("─".repeat(36))}`);
|
|
269
|
+
// Group workers by agent
|
|
270
|
+
const workersByAgent = new Map();
|
|
271
|
+
for (const row of payload.workers) {
|
|
272
|
+
const list = workersByAgent.get(row.agent) ?? [];
|
|
273
|
+
list.push(row);
|
|
274
|
+
workersByAgent.set(row.agent, list);
|
|
275
|
+
}
|
|
276
|
+
const allWorkerNames = payload.workers.map((r) => r.worker);
|
|
277
|
+
const workerNameWidth = Math.max(12, ...allWorkerNames.map((n) => n.length));
|
|
278
|
+
for (const [agent, rows] of workersByAgent) {
|
|
279
|
+
lines.push(` ${bold(agent)}`);
|
|
280
|
+
for (const row of rows) {
|
|
281
|
+
const name = row.worker.padEnd(workerNameWidth);
|
|
282
|
+
const dot = statusDot(row.status);
|
|
283
|
+
const pidStr = row.pid !== null ? `pid ${row.pid}` : "";
|
|
284
|
+
const restartStr = `restarts: ${row.restartCount}`;
|
|
285
|
+
/* v8 ignore start — exit info branches tested via daemon-crash-context; v8 misreports conditional chains @preserve */
|
|
286
|
+
let exitStr = "";
|
|
287
|
+
if (row.lastExitCode !== null)
|
|
288
|
+
exitStr = `exit=${row.lastExitCode}`;
|
|
289
|
+
if (row.lastSignal !== null)
|
|
290
|
+
exitStr = row.lastExitCode !== null ? `exit=${row.lastExitCode} sig=${row.lastSignal}` : `sig=${row.lastSignal}`;
|
|
291
|
+
/* v8 ignore stop */
|
|
292
|
+
const details = [pidStr, restartStr, exitStr].filter(Boolean).join(" ");
|
|
293
|
+
lines.push(` ${name} ${dot} ${row.status.padEnd(10)} ${dim(details)}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
lines.push("");
|
|
297
|
+
}
|
|
298
|
+
return lines.join("\n");
|
|
218
299
|
}
|
|
219
300
|
function formatVersionOutput() {
|
|
220
301
|
const version = (0, runtime_metadata_1.getRuntimeMetadata)().version;
|
|
@@ -240,6 +321,8 @@ function buildStoppedStatusPayload(socketPath) {
|
|
|
240
321
|
senseCount: 0,
|
|
241
322
|
entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
|
|
242
323
|
mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
|
|
324
|
+
syncEnabled: false,
|
|
325
|
+
syncRemote: "origin",
|
|
243
326
|
},
|
|
244
327
|
senses: [],
|
|
245
328
|
workers: [],
|
|
@@ -42,6 +42,7 @@ const net = __importStar(require("net"));
|
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const identity_1 = require("../identity");
|
|
45
|
+
const config_1 = require("../config");
|
|
45
46
|
const runtime_1 = require("../../nerves/runtime");
|
|
46
47
|
const runtime_metadata_1 = require("./runtime-metadata");
|
|
47
48
|
const runtime_mode_1 = require("./runtime-mode");
|
|
@@ -233,6 +234,7 @@ class OuroDaemon {
|
|
|
233
234
|
const workers = buildWorkerRows(snapshots);
|
|
234
235
|
const senses = this.senseManager?.listSenseRows() ?? [];
|
|
235
236
|
const repoRoot = (0, identity_1.getRepoRoot)();
|
|
237
|
+
const syncConfig = (0, config_1.getSyncConfig)();
|
|
236
238
|
return {
|
|
237
239
|
overview: {
|
|
238
240
|
daemon: "running",
|
|
@@ -244,6 +246,8 @@ class OuroDaemon {
|
|
|
244
246
|
senseCount: senses.length,
|
|
245
247
|
entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
|
|
246
248
|
mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
|
|
249
|
+
syncEnabled: syncConfig.enabled,
|
|
250
|
+
syncRemote: syncConfig.remote,
|
|
247
251
|
},
|
|
248
252
|
workers,
|
|
249
253
|
senses,
|
|
@@ -318,7 +318,6 @@ exports.adoSemanticToolDefinitions = [
|
|
|
318
318
|
return result;
|
|
319
319
|
},
|
|
320
320
|
integration: "ado",
|
|
321
|
-
confirmationRequired: true,
|
|
322
321
|
summaryKeys: ["organization", "project", "title"],
|
|
323
322
|
},
|
|
324
323
|
// -- ado_create_issue --
|
|
@@ -360,7 +359,6 @@ exports.adoSemanticToolDefinitions = [
|
|
|
360
359
|
return result;
|
|
361
360
|
},
|
|
362
361
|
integration: "ado",
|
|
363
|
-
confirmationRequired: true,
|
|
364
362
|
summaryKeys: ["organization", "project", "title"],
|
|
365
363
|
},
|
|
366
364
|
// -- ado_move_items --
|
|
@@ -415,7 +413,6 @@ exports.adoSemanticToolDefinitions = [
|
|
|
415
413
|
return JSON.stringify({ moved, errors, organization: adoCtx.organization, project: adoCtx.project });
|
|
416
414
|
},
|
|
417
415
|
integration: "ado",
|
|
418
|
-
confirmationRequired: true,
|
|
419
416
|
summaryKeys: ["organization", "project", "workItemIds"],
|
|
420
417
|
},
|
|
421
418
|
// -- ado_restructure_backlog --
|
|
@@ -474,7 +471,6 @@ exports.adoSemanticToolDefinitions = [
|
|
|
474
471
|
return JSON.stringify({ results, organization: adoCtx.organization, project: adoCtx.project });
|
|
475
472
|
},
|
|
476
473
|
integration: "ado",
|
|
477
|
-
confirmationRequired: true,
|
|
478
474
|
summaryKeys: ["organization", "project"],
|
|
479
475
|
},
|
|
480
476
|
// -- ado_validate_structure --
|
|
@@ -676,7 +672,6 @@ exports.adoSemanticToolDefinitions = [
|
|
|
676
672
|
return JSON.stringify({ results, organization: adoCtx.organization, project: adoCtx.project });
|
|
677
673
|
},
|
|
678
674
|
integration: "ado",
|
|
679
|
-
confirmationRequired: true,
|
|
680
675
|
summaryKeys: ["organization", "project"],
|
|
681
676
|
},
|
|
682
677
|
// -- ado_detect_orphans --
|
|
@@ -118,7 +118,7 @@ exports.configToolDefinitions = [
|
|
|
118
118
|
type: "function",
|
|
119
119
|
function: {
|
|
120
120
|
name: "update_config",
|
|
121
|
-
description: "Update
|
|
121
|
+
description: "Update an agent configuration value. Managed keys (version, enabled) cannot be modified.",
|
|
122
122
|
parameters: {
|
|
123
123
|
type: "object",
|
|
124
124
|
properties: {
|
|
@@ -152,15 +152,10 @@ exports.configToolDefinitions = [
|
|
|
152
152
|
return `Error: validation failed for "${a.path}": ${validationError}`;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
//
|
|
156
|
-
if (entry.tier ===
|
|
157
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused
|
|
158
|
-
return `
|
|
159
|
-
}
|
|
160
|
-
// Tier 2: refuse — must use propose_config
|
|
161
|
-
if (entry.tier === 2) {
|
|
162
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused T2 change to ${a.path} — use propose_config`, meta: { path: a.path, tier: 2 } });
|
|
163
|
-
return `Refused: "${a.path}" is a Tier 2 (proposal) key that requires operator approval. Use propose_config instead of update_config to propose this change.`;
|
|
155
|
+
// Managed: refuse
|
|
156
|
+
if (entry.tier === "managed") {
|
|
157
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused managed change to ${a.path}`, meta: { path: a.path, tier: "managed" } });
|
|
158
|
+
return `This key is managed by the harness and cannot be modified directly.`;
|
|
164
159
|
}
|
|
165
160
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
166
161
|
const configPath = path.join(agentRoot, "agent.json");
|
|
@@ -172,7 +167,7 @@ exports.configToolDefinitions = [
|
|
|
172
167
|
/* v8 ignore next -- defensive: agent.json read failure in update_config @preserve */
|
|
173
168
|
return `Error: failed to read agent.json at ${configPath}`;
|
|
174
169
|
}
|
|
175
|
-
// Apply
|
|
170
|
+
// Apply change immediately
|
|
176
171
|
const parts = entry.path.split(".");
|
|
177
172
|
let target = rawConfig;
|
|
178
173
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -187,80 +182,4 @@ exports.configToolDefinitions = [
|
|
|
187
182
|
return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change applied immediately.`;
|
|
188
183
|
},
|
|
189
184
|
},
|
|
190
|
-
{
|
|
191
|
-
tool: {
|
|
192
|
-
type: "function",
|
|
193
|
-
function: {
|
|
194
|
-
name: "propose_config",
|
|
195
|
-
description: "Propose a change to a Tier 2 (proposal) agent configuration value. Requires operator approval before the change is applied. For Tier 1 self-service keys, use update_config instead.",
|
|
196
|
-
parameters: {
|
|
197
|
-
type: "object",
|
|
198
|
-
properties: {
|
|
199
|
-
path: { type: "string", description: "Config key in dot-notation (e.g., 'context.maxTokens', 'humanFacing.model')" },
|
|
200
|
-
value: { type: "string", description: "New value as JSON (e.g., '120000', '\"azure\"', '{\"enabled\": true}')" },
|
|
201
|
-
},
|
|
202
|
-
required: ["path", "value"],
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
confirmationRequired: true,
|
|
207
|
-
confirmationAlwaysRequired: true,
|
|
208
|
-
handler: (a) => {
|
|
209
|
-
const entry = (0, config_registry_1.getRegistryEntry)(a.path);
|
|
210
|
-
if (!entry) {
|
|
211
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `unknown config path: ${a.path}`, meta: { path: a.path } });
|
|
212
|
-
return `Error: unknown config path "${a.path}". Use read_config to see available paths.`;
|
|
213
|
-
}
|
|
214
|
-
let parsedValue;
|
|
215
|
-
try {
|
|
216
|
-
parsedValue = JSON.parse(a.value);
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `invalid JSON value for ${a.path}`, meta: { path: a.path, value: a.value } });
|
|
220
|
-
return `Error: invalid JSON value. Provide value as valid JSON (e.g., 120000, "azure", {"enabled": true}).`;
|
|
221
|
-
}
|
|
222
|
-
// Validate value against registry entry's validator
|
|
223
|
-
/* v8 ignore next -- all current entries have validators; guard kept for future entries without one @preserve */
|
|
224
|
-
if (entry.validate) {
|
|
225
|
-
const validationError = entry.validate(parsedValue);
|
|
226
|
-
if (validationError) {
|
|
227
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `validation failed for ${a.path}: ${validationError}`, meta: { path: a.path, value: a.value, validationError } });
|
|
228
|
-
return `Error: validation failed for "${a.path}": ${validationError}`;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Tier 3: refuse
|
|
232
|
-
if (entry.tier === 3) {
|
|
233
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
|
|
234
|
-
return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
|
|
235
|
-
}
|
|
236
|
-
// Tier 1: refuse — use update_config for self-service keys
|
|
237
|
-
if (entry.tier === 1) {
|
|
238
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T1 change to ${a.path} — use update_config`, meta: { path: a.path, tier: 1 } });
|
|
239
|
-
return `Refused: "${a.path}" is a Tier 1 (self-service) key. Use update_config instead — no operator approval needed.`;
|
|
240
|
-
}
|
|
241
|
-
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
242
|
-
const configPath = path.join(agentRoot, "agent.json");
|
|
243
|
-
let rawConfig;
|
|
244
|
-
try {
|
|
245
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
/* v8 ignore next -- defensive: agent.json read failure in propose_config @preserve */
|
|
249
|
-
return `Error: failed to read agent.json at ${configPath}`;
|
|
250
|
-
}
|
|
251
|
-
// Apply T2 change (operator confirmation already obtained via confirmationAlwaysRequired)
|
|
252
|
-
const parts = entry.path.split(".");
|
|
253
|
-
let target = rawConfig;
|
|
254
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
255
|
-
if (!(parts[i] in target) || typeof target[parts[i]] !== "object" || target[parts[i]] === null) {
|
|
256
|
-
target[parts[i]] = {};
|
|
257
|
-
}
|
|
258
|
-
target = target[parts[i]];
|
|
259
|
-
}
|
|
260
|
-
target[parts[parts.length - 1]] = parsedValue;
|
|
261
|
-
fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n", "utf-8");
|
|
262
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_applied", message: `applied approved config change to ${a.path}`, meta: { path: a.path, tier: entry.tier, value: parsedValue } });
|
|
263
|
-
return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change approved and applied.`;
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
185
|
];
|
|
@@ -42,7 +42,6 @@ exports.githubToolDefinitions = [
|
|
|
42
42
|
return (0, github_client_1.githubRequest)(ctx.githubToken, "POST", `/repos/${owner}/${repo}/issues`, JSON.stringify(payload));
|
|
43
43
|
},
|
|
44
44
|
integration: "github",
|
|
45
|
-
confirmationRequired: true,
|
|
46
45
|
summaryKeys: ["title"],
|
|
47
46
|
},
|
|
48
47
|
];
|
|
@@ -64,7 +64,6 @@ exports.teamsToolDefinitions = [
|
|
|
64
64
|
return (0, graph_client_1.graphRequest)(ctx.graphToken, args.method, args.path, args.body);
|
|
65
65
|
},
|
|
66
66
|
integration: "graph",
|
|
67
|
-
confirmationRequired: true,
|
|
68
67
|
summaryKeys: ["method", "path"],
|
|
69
68
|
},
|
|
70
69
|
// -- Generic ADO tools --
|
|
@@ -126,7 +125,6 @@ exports.teamsToolDefinitions = [
|
|
|
126
125
|
return (0, ado_client_1.adoRequest)(ctx.adoToken, args.method, args.organization, args.path, args.body, args.host);
|
|
127
126
|
},
|
|
128
127
|
integration: "ado",
|
|
129
|
-
confirmationRequired: true,
|
|
130
128
|
summaryKeys: ["method", "organization", "path"],
|
|
131
129
|
},
|
|
132
130
|
// -- Convenience aliases (backward compat) --
|
|
@@ -237,7 +235,6 @@ exports.teamsToolDefinitions = [
|
|
|
237
235
|
}
|
|
238
236
|
},
|
|
239
237
|
/* v8 ignore stop */
|
|
240
|
-
confirmationRequired: true,
|
|
241
238
|
summaryKeys: ["user_name", "user_id"],
|
|
242
239
|
},
|
|
243
240
|
// -- Documentation tools --
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -3,8 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.surfaceToolDef = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
|
|
4
4
|
exports.resetMcpDefinitions = resetMcpDefinitions;
|
|
5
5
|
exports.getToolsForChannel = getToolsForChannel;
|
|
6
|
-
exports.isConfirmationRequired = isConfirmationRequired;
|
|
7
|
-
exports.isConfirmationAlwaysRequired = isConfirmationAlwaysRequired;
|
|
8
6
|
exports.execTool = execTool;
|
|
9
7
|
exports.summarizeArgs = summarizeArgs;
|
|
10
8
|
exports.buildToolResultSummary = buildToolResultSummary;
|
|
@@ -39,7 +37,7 @@ Object.defineProperty(exports, "surfaceToolDef", { enumerable: true, get: functi
|
|
|
39
37
|
// All tool definitions in a single registry
|
|
40
38
|
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, tools_surface_1.surfaceToolDefinition];
|
|
41
39
|
// MCP tool definitions — populated each time getToolsForChannel() is called with an mcpManager.
|
|
42
|
-
// Kept separate from allDefinitions so execTool
|
|
40
|
+
// Kept separate from allDefinitions so execTool can find them.
|
|
43
41
|
let mcpDefinitions = [];
|
|
44
42
|
/** Exported for testing — reset the MCP definitions cache. */
|
|
45
43
|
function resetMcpDefinitions() {
|
|
@@ -120,18 +118,6 @@ function findDefinition(toolName) {
|
|
|
120
118
|
return allDefinitions.find((d) => d.tool.function.name === toolName)
|
|
121
119
|
?? mcpDefinitions.find((d) => d.tool.function.name === toolName);
|
|
122
120
|
}
|
|
123
|
-
// Check whether a tool requires user confirmation before execution.
|
|
124
|
-
// Reads from ToolDefinition.confirmationRequired instead of a separate Set.
|
|
125
|
-
function isConfirmationRequired(toolName) {
|
|
126
|
-
const def = findDefinition(toolName);
|
|
127
|
-
return def?.confirmationRequired === true;
|
|
128
|
-
}
|
|
129
|
-
// Check whether a tool's confirmation is non-bypassable (cannot be skipped by skipConfirmation).
|
|
130
|
-
// Used for T2 config proposals that must always require operator approval.
|
|
131
|
-
function isConfirmationAlwaysRequired(toolName) {
|
|
132
|
-
const def = findDefinition(toolName);
|
|
133
|
-
return def?.confirmationAlwaysRequired === true;
|
|
134
|
-
}
|
|
135
121
|
function normalizeGuardArgs(_name, args) {
|
|
136
122
|
return args;
|
|
137
123
|
}
|
package/dist/senses/teams.js
CHANGED
|
@@ -41,7 +41,6 @@ exports.sanitizeFeedbackComment = sanitizeFeedbackComment;
|
|
|
41
41
|
exports.buildFeedbackSyntheticText = buildFeedbackSyntheticText;
|
|
42
42
|
exports.buildWelcomeCard = buildWelcomeCard;
|
|
43
43
|
exports.createTeamsCallbacks = createTeamsCallbacks;
|
|
44
|
-
exports.resolvePendingConfirmation = resolvePendingConfirmation;
|
|
45
44
|
exports.withConversationLock = withConversationLock;
|
|
46
45
|
exports.handleTeamsMessage = handleTeamsMessage;
|
|
47
46
|
exports.sendProactiveTeamsMessageToSession = sendProactiveTeamsMessageToSession;
|
|
@@ -479,24 +478,6 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
479
478
|
safeSend(msg);
|
|
480
479
|
}
|
|
481
480
|
},
|
|
482
|
-
onConfirmAction: options?.conversationId
|
|
483
|
-
? async (name, args) => {
|
|
484
|
-
const convId = options.conversationId;
|
|
485
|
-
const argsDesc = Object.entries(args).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
486
|
-
safeUpdate(`Confirm action: ${name} (${argsDesc}) -- reply "yes" to confirm or "no" to cancel`);
|
|
487
|
-
return new Promise((resolve) => {
|
|
488
|
-
_pendingConfirmations.set(convId, resolve);
|
|
489
|
-
// Auto-deny after 2 minutes to prevent indefinite blocking
|
|
490
|
-
// (e.g. when the stream dies and the user never sees the prompt).
|
|
491
|
-
setTimeout(() => {
|
|
492
|
-
if (_pendingConfirmations.has(convId)) {
|
|
493
|
-
_pendingConfirmations.delete(convId);
|
|
494
|
-
resolve("denied");
|
|
495
|
-
}
|
|
496
|
-
}, 120_000);
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
: undefined,
|
|
500
481
|
flush: async () => {
|
|
501
482
|
stopFlushTimer();
|
|
502
483
|
stopPhraseRotation();
|
|
@@ -534,26 +515,6 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
534
515
|
},
|
|
535
516
|
};
|
|
536
517
|
}
|
|
537
|
-
// Per-conversation pending confirmation resolvers.
|
|
538
|
-
// When a mutate tool needs confirmation, the resolver is stored here.
|
|
539
|
-
// The next message from the same conversation resolves it.
|
|
540
|
-
const _pendingConfirmations = new Map();
|
|
541
|
-
// Confirmation response words (case-insensitive)
|
|
542
|
-
const CONFIRM_WORDS = new Set(["yes", "confirm", "go", "y", "ok", "approve", "proceed"]);
|
|
543
|
-
function resolvePendingConfirmation(convId, text) {
|
|
544
|
-
const resolver = _pendingConfirmations.get(convId);
|
|
545
|
-
if (!resolver)
|
|
546
|
-
return false;
|
|
547
|
-
_pendingConfirmations.delete(convId);
|
|
548
|
-
const word = text.trim().toLowerCase();
|
|
549
|
-
if (CONFIRM_WORDS.has(word)) {
|
|
550
|
-
resolver("confirmed");
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
resolver("denied");
|
|
554
|
-
}
|
|
555
|
-
return true;
|
|
556
|
-
}
|
|
557
518
|
const _turnCoordinator = (0, turn_coordinator_1.createTurnCoordinator)();
|
|
558
519
|
function teamsTurnKey(conversationId) {
|
|
559
520
|
return `teams:${conversationId}`;
|
|
@@ -659,8 +620,6 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
659
620
|
},
|
|
660
621
|
...(reactionOverrides?.isReactionSignal ? { isReactionSignal: true } : {}),
|
|
661
622
|
};
|
|
662
|
-
if (channelConfig.skipConfirmation)
|
|
663
|
-
agentOptions.skipConfirmation = true;
|
|
664
623
|
// ── Call shared pipeline ──────────────────────────────────────────
|
|
665
624
|
// Capture terminal errors — failover message replaces the error card if it triggers
|
|
666
625
|
let capturedTerminalError = null;
|
|
@@ -961,13 +920,6 @@ function registerBotHandlers(app, label) {
|
|
|
961
920
|
const userId = activity.from?.id || "";
|
|
962
921
|
const channelId = activity.channelId || "msteams";
|
|
963
922
|
(0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.message_received", component: "channels", message: `[${label}] incoming teams message`, meta: { userId: userId.slice(0, 12), conversationId: convId.slice(0, 20) } });
|
|
964
|
-
// Resolve pending confirmations IMMEDIATELY — before token fetches or
|
|
965
|
-
// the conversation lock. The original message holds the lock while
|
|
966
|
-
// awaiting confirmation, so acquiring it here would deadlock. Token
|
|
967
|
-
// fetches are also unnecessary (and slow) for a simple yes/no reply.
|
|
968
|
-
if (resolvePendingConfirmation(convId, text)) {
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
923
|
const commandRegistry = createTeamsCommandRegistry();
|
|
972
924
|
const parsedSlashCommand = (0, commands_1.parseSlashCommand)(text);
|
|
973
925
|
if (parsedSlashCommand) {
|