@ouro.bot/cli 0.1.0-alpha.903 → 0.1.0-alpha.905
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 +12 -0
- package/deploy/unraid/sanctuary-replay-gate.mjs +26 -1
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/core.js +63 -13
- package/dist/heart/daemon/cli-defaults.js +17 -0
- package/dist/heart/daemon/cli-exec.js +68 -3
- package/dist/heart/daemon/cli-help.js +6 -0
- package/dist/heart/daemon/cli-parse.js +7 -0
- package/dist/heart/reply-style.js +1 -5
- package/dist/heart/shelf-claims.js +78 -0
- package/dist/heart/versioning/ouro-version-manager.js +27 -4
- package/dist/heart/versioning/release-sources.js +173 -0
- package/dist/heart/versioning/self-update-defaults.js +294 -0
- package/dist/heart/versioning/self-update.js +196 -0
- package/dist/heart/versioning/update-state.js +126 -0
- package/dist/heart/versioning/updater-launchd.js +150 -0
- package/dist/senses/sanctuary-media-catalog-contract.js +5 -43
- package/dist/senses/sanctuary-runtime.js +2 -2
- package/dist/senses/telegram-handoff.js +80 -0
- package/dist/senses/telegram.js +57 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
|
@@ -0,0 +1,126 @@
|
|
|
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.UPDATER_INTERVAL_SECONDS = void 0;
|
|
37
|
+
exports.updateStatePath = updateStatePath;
|
|
38
|
+
exports.readUpdateState = readUpdateState;
|
|
39
|
+
exports.writeUpdateState = writeUpdateState;
|
|
40
|
+
exports.formatUpdateStatusLine = formatUpdateStatusLine;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
45
|
+
exports.UPDATER_INTERVAL_SECONDS = 60 * 60;
|
|
46
|
+
function updateStatePath(homeDir = os.homedir()) {
|
|
47
|
+
return path.join(homeDir, ".ouro-cli", "update-state.json");
|
|
48
|
+
}
|
|
49
|
+
function readUpdateState(deps = {}) {
|
|
50
|
+
const readFileSync = deps.readFileSync ?? fs.readFileSync;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(readFileSync(updateStatePath(deps.homeDir), "utf8"));
|
|
53
|
+
if (parsed?.schemaVersion !== 1 || typeof parsed.lastRunAt !== "string" || typeof parsed.outcome !== "string")
|
|
54
|
+
return null;
|
|
55
|
+
return {
|
|
56
|
+
...parsed,
|
|
57
|
+
failedVersions: Array.isArray(parsed.failedVersions) ? parsed.failedVersions.filter((v) => typeof v === "string") : [],
|
|
58
|
+
consecutiveFailures: typeof parsed.consecutiveFailures === "number" ? parsed.consecutiveFailures : 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function writeUpdateState(state, deps = {}) {
|
|
66
|
+
const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
|
|
67
|
+
const renameSync = deps.renameSync ?? fs.renameSync;
|
|
68
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
69
|
+
const destination = updateStatePath(deps.homeDir);
|
|
70
|
+
mkdirSync(path.dirname(destination), { recursive: true });
|
|
71
|
+
const temporary = `${destination}.${process.pid}.tmp`;
|
|
72
|
+
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
73
|
+
renameSync(temporary, destination);
|
|
74
|
+
(0, runtime_1.emitNervesEvent)({
|
|
75
|
+
level: ["updated", "current", "pinned"].includes(state.outcome) ? "info" : "warn",
|
|
76
|
+
component: "daemon",
|
|
77
|
+
event: "daemon.self_update_state_written",
|
|
78
|
+
message: "recorded the unattended updater outcome",
|
|
79
|
+
meta: { outcome: state.outcome, currentVersion: state.currentVersion, latestVersion: state.latestVersion ?? null, detail: state.detail ?? null },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function formatAge(ms) {
|
|
83
|
+
const minutes = Math.max(0, Math.round(ms / 60_000));
|
|
84
|
+
if (minutes < 60)
|
|
85
|
+
return `${minutes}m ago`;
|
|
86
|
+
const hours = Math.round(minutes / 60);
|
|
87
|
+
if (hours < 48)
|
|
88
|
+
return `${hours}h ago`;
|
|
89
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* One line for `ouro status`. Problems lead with "needs attention" so a
|
|
93
|
+
* stuck machine is visible at a glance.
|
|
94
|
+
*/
|
|
95
|
+
function formatUpdateStatusLine(state, options) {
|
|
96
|
+
if (!state) {
|
|
97
|
+
return options.updaterInstalled === false
|
|
98
|
+
? "Updates: needs attention: the unattended updater is not installed; run `ouro self-update` once"
|
|
99
|
+
: "Updates: no update check recorded yet; run `ouro self-update`";
|
|
100
|
+
}
|
|
101
|
+
const ageMs = options.now - Date.parse(state.lastRunAt);
|
|
102
|
+
const age = formatAge(ageMs);
|
|
103
|
+
const stale = ageMs > exports.UPDATER_INTERVAL_SECONDS * 1000 * 3;
|
|
104
|
+
const version = state.currentVersion ?? "unknown version";
|
|
105
|
+
let line;
|
|
106
|
+
switch (state.outcome) {
|
|
107
|
+
case "updated":
|
|
108
|
+
line = `Updates: updated to ${version} ${age}${state.source ? ` via ${state.source}` : ""}`;
|
|
109
|
+
break;
|
|
110
|
+
case "current":
|
|
111
|
+
line = `Updates: current at ${version} (checked ${age}${state.source ? ` via ${state.source}` : ""})`;
|
|
112
|
+
break;
|
|
113
|
+
case "pinned":
|
|
114
|
+
line = `Updates: pinned to ${version} (checked ${age}; ${state.detail ?? "pin honored"})`;
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
line = `Updates: needs attention: ${state.outcome} ${age} on ${version}${state.latestVersion ? ` (latest ${state.latestVersion})` : ""}: ${state.detail ?? "no detail"}`;
|
|
118
|
+
}
|
|
119
|
+
if (stale) {
|
|
120
|
+
line += `; needs attention: the updater has not run since ${state.lastRunAt}`;
|
|
121
|
+
}
|
|
122
|
+
if (options.updaterInstalled === false) {
|
|
123
|
+
line += "; needs attention: the unattended updater is not installed; run `ouro self-update` once";
|
|
124
|
+
}
|
|
125
|
+
return line;
|
|
126
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
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.STABLE_NODE_PATHS = exports.UPDATER_PLIST_LABEL = void 0;
|
|
37
|
+
exports.chooseUpdaterNodePath = chooseUpdaterNodePath;
|
|
38
|
+
exports.updaterPlistPath = updaterPlistPath;
|
|
39
|
+
exports.generateUpdaterPlist = generateUpdaterPlist;
|
|
40
|
+
exports.ensureUpdaterAgent = ensureUpdaterAgent;
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
const update_state_1 = require("./update-state");
|
|
44
|
+
/**
|
|
45
|
+
* A launchd agent that runs `ouro self-update --unattended` every hour and at
|
|
46
|
+
* login, independent of the Ouro daemon. Updates keep flowing when the
|
|
47
|
+
* daemon is stopped, crashed, or was never started on this machine.
|
|
48
|
+
*/
|
|
49
|
+
exports.UPDATER_PLIST_LABEL = "bot.ouro.updater";
|
|
50
|
+
/** Node binaries that survive nvm switches and Node upgrades, in preference order. */
|
|
51
|
+
exports.STABLE_NODE_PATHS = ["/opt/homebrew/bin/node", "/usr/local/bin/node"];
|
|
52
|
+
/**
|
|
53
|
+
* Pick the node binary the updater agent runs. A stable path (Homebrew) is
|
|
54
|
+
* preferred when it exists and runs the same major Node version as this
|
|
55
|
+
* process, so the plist does not churn every time another nvm node runs
|
|
56
|
+
* `ouro up`; otherwise the running node is used.
|
|
57
|
+
*/
|
|
58
|
+
function chooseUpdaterNodePath(options) {
|
|
59
|
+
for (const candidate of options.candidates ?? exports.STABLE_NODE_PATHS) {
|
|
60
|
+
if (options.majorOf(candidate) === options.runningMajor)
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
63
|
+
return options.execPath;
|
|
64
|
+
}
|
|
65
|
+
function updaterPlistPath(homeDir) {
|
|
66
|
+
return path.join(homeDir, "Library", "LaunchAgents", `${exports.UPDATER_PLIST_LABEL}.plist`);
|
|
67
|
+
}
|
|
68
|
+
function xmlEscape(value) {
|
|
69
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
70
|
+
}
|
|
71
|
+
function generateUpdaterPlist(options) {
|
|
72
|
+
const launcher = path.join(options.homeDir, ".ouro-cli", "bin", "ouro-launcher.js");
|
|
73
|
+
const envPath = [path.dirname(options.nodePath), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"].join(":");
|
|
74
|
+
return [
|
|
75
|
+
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
76
|
+
`<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">`,
|
|
77
|
+
`<plist version="1.0">`,
|
|
78
|
+
`<dict>`,
|
|
79
|
+
` <key>Label</key>`,
|
|
80
|
+
` <string>${exports.UPDATER_PLIST_LABEL}</string>`,
|
|
81
|
+
` <key>ProgramArguments</key>`,
|
|
82
|
+
` <array>`,
|
|
83
|
+
` <string>${xmlEscape(options.nodePath)}</string>`,
|
|
84
|
+
` <string>${xmlEscape(launcher)}</string>`,
|
|
85
|
+
` <string>self-update</string>`,
|
|
86
|
+
` <string>--unattended</string>`,
|
|
87
|
+
` </array>`,
|
|
88
|
+
` <key>RunAtLoad</key>`,
|
|
89
|
+
` <true/>`,
|
|
90
|
+
` <key>StartInterval</key>`,
|
|
91
|
+
` <integer>${update_state_1.UPDATER_INTERVAL_SECONDS}</integer>`,
|
|
92
|
+
` <key>ProcessType</key>`,
|
|
93
|
+
` <string>Background</string>`,
|
|
94
|
+
` <key>EnvironmentVariables</key>`,
|
|
95
|
+
` <dict>`,
|
|
96
|
+
` <key>PATH</key>`,
|
|
97
|
+
` <string>${xmlEscape(envPath)}</string>`,
|
|
98
|
+
` </dict>`,
|
|
99
|
+
`</dict>`,
|
|
100
|
+
`</plist>`,
|
|
101
|
+
``,
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write the updater plist when it is missing or stale (for example after the
|
|
106
|
+
* node binary moved) and load it. When the updater agent itself is running,
|
|
107
|
+
* or another process holds the update lock, reloading would kill the run in
|
|
108
|
+
* progress, so it only rewrites the file and the next interactive
|
|
109
|
+
* `ouro self-update` or `ouro up` reloads it.
|
|
110
|
+
*/
|
|
111
|
+
function ensureUpdaterAgent(deps) {
|
|
112
|
+
const plistPath = updaterPlistPath(deps.homeDir);
|
|
113
|
+
const desired = generateUpdaterPlist({ homeDir: deps.homeDir, nodePath: deps.nodePath });
|
|
114
|
+
const existing = deps.readFile(plistPath);
|
|
115
|
+
const domain = `gui/${deps.userUid}`;
|
|
116
|
+
let loaded = true;
|
|
117
|
+
try {
|
|
118
|
+
deps.exec(`launchctl print ${domain}/${exports.UPDATER_PLIST_LABEL}`);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
loaded = false;
|
|
122
|
+
}
|
|
123
|
+
if (existing === desired && loaded)
|
|
124
|
+
return "unchanged";
|
|
125
|
+
deps.mkdirp(path.dirname(plistPath));
|
|
126
|
+
deps.writeFile(plistPath, desired);
|
|
127
|
+
if (deps.runningUnderUpdaterAgent || deps.updateInProgress) {
|
|
128
|
+
(0, runtime_1.emitNervesEvent)({
|
|
129
|
+
component: "daemon",
|
|
130
|
+
event: "daemon.self_update_agent_written",
|
|
131
|
+
message: "rewrote the updater launch agent; reload waits for the next interactive run",
|
|
132
|
+
meta: { plistPath, updateInProgress: deps.updateInProgress },
|
|
133
|
+
});
|
|
134
|
+
return "written-reload-pending";
|
|
135
|
+
}
|
|
136
|
+
if (loaded) {
|
|
137
|
+
try {
|
|
138
|
+
deps.exec(`launchctl bootout ${domain}/${exports.UPDATER_PLIST_LABEL}`);
|
|
139
|
+
}
|
|
140
|
+
catch { /* already gone */ }
|
|
141
|
+
}
|
|
142
|
+
deps.exec(`launchctl bootstrap ${domain} "${plistPath}"`);
|
|
143
|
+
(0, runtime_1.emitNervesEvent)({
|
|
144
|
+
component: "daemon",
|
|
145
|
+
event: "daemon.self_update_agent_installed",
|
|
146
|
+
message: "installed the unattended updater launch agent",
|
|
147
|
+
meta: { plistPath, intervalSeconds: update_state_1.UPDATER_INTERVAL_SECONDS },
|
|
148
|
+
});
|
|
149
|
+
return "installed";
|
|
150
|
+
}
|
|
@@ -5,10 +5,8 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
5
5
|
const sanctuary_media_optimization_1 = require("./sanctuary-media-optimization");
|
|
6
6
|
const LEGACY_CATALOG_TOOL = "sanctuary_search_media_catalog";
|
|
7
7
|
const MEDIA_SEARCH_TOOL = "media_search";
|
|
8
|
-
const MEDIA_REQUEST_TOOL = "media_request";
|
|
9
8
|
const HOUSEHOLD_JARGON = /\b(?:bounded|catalog|inventory|endpoint|backend|data shape|pars(?:e|ed|ing)|json|jellyfin|unmanic|sanctuary_search_media_catalog)\b/iu;
|
|
10
9
|
const NEGATED_MEDIA_ACCESS = /\b(?:not|never|none|nothing|zero|inaccessible|invisible|unavailable|broken|failed|failing)\b|\b(?:isn[’']t|won[’']t)\b|\bno\s+access\b|\b(?:lost|lacks?|lacking|without)\s+access\b|\bonly\s+\d|\bfewer\s+than\b|\bsome\s+of\b|\b(?:part|portion|subset)\s+of\b|\bi\s+(?:can(?:not|[’']t)|do\s+not|don[’']t|have\s+no)\b|\bi[’']m\s+not\b/iu;
|
|
11
|
-
const UNSOLICITED_PIVOT = /\b(?:what would you like|what are you in the mood for|would you like me to|do you want me to|want me to|recommend we add)\b/iu;
|
|
12
10
|
const REPORT_LABEL = /(?:^|\n)\s*(?:version|house|the house|render check|status|result)\s*:/iu;
|
|
13
11
|
const TITLE_LIST_GLUE = new Set(["and", "are", "catalog", "film", "films", "from", "have", "here", "in", "is", "library", "movie", "movies", "on", "shelf", "show", "shows", "the", "these", "titles", "we"]);
|
|
14
12
|
const COUNT_ANSWER_GLUE = new Set(["and", "are", "currently", "episodes", "film", "films", "have", "in", "items", "library", "movie", "movies", "on", "shelf", "show", "shows", "the", "there", "tv", "we"]);
|
|
@@ -86,7 +84,6 @@ function parseCatalogEvidence(result, args) {
|
|
|
86
84
|
totalItems: parsed.data.totalItems,
|
|
87
85
|
matchedItems: parsed.data.matchedItems,
|
|
88
86
|
titles,
|
|
89
|
-
offShelfTitles: [],
|
|
90
87
|
fromMediaSearch: false,
|
|
91
88
|
};
|
|
92
89
|
}
|
|
@@ -105,7 +102,6 @@ function parseMediaSearchEvidence(result, args) {
|
|
|
105
102
|
if (!Number.isSafeInteger(parsed.matched) || !Array.isArray(parsed.items))
|
|
106
103
|
return null;
|
|
107
104
|
const titles = [];
|
|
108
|
-
const offShelfTitles = [];
|
|
109
105
|
for (const entry of parsed.items) {
|
|
110
106
|
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
111
107
|
continue;
|
|
@@ -113,15 +109,12 @@ function parseMediaSearchEvidence(result, args) {
|
|
|
113
109
|
if (typeof item.title !== "string" || !item.title.trim())
|
|
114
110
|
continue;
|
|
115
111
|
titles.push(item.title.trim());
|
|
116
|
-
if (item.on_shelf === false)
|
|
117
|
-
offShelfTitles.push(item.title.trim());
|
|
118
112
|
}
|
|
119
113
|
return {
|
|
120
114
|
query: (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(args.query ?? ""),
|
|
121
115
|
totalItems: parsed.matched,
|
|
122
116
|
matchedItems: parsed.matched,
|
|
123
117
|
titles,
|
|
124
|
-
offShelfTitles,
|
|
125
118
|
fromMediaSearch: true,
|
|
126
119
|
};
|
|
127
120
|
}
|
|
@@ -150,14 +143,9 @@ function hasGroundedFirstPersonVisibility(answer, formattedCount) {
|
|
|
150
143
|
return firstPersonAccess.some((pattern) => pattern.test(answer)) && groundedCount.test(answer);
|
|
151
144
|
}
|
|
152
145
|
function commonAnswerRejection(answer, technicalDetailRequested) {
|
|
153
|
-
if (/\b(?:just|only|actually|merely)\s+(?:a\s+)?(?:bot|ai|assistant)\b|\bi\s+(?:can[’']?t|cannot|don[’']?t|do not)\s+(?:actually\s+)?watch\b|\bif i had to pick\b/iu.test(answer)) {
|
|
154
|
-
return "Answer from the catalog evidence with a confident, personable choice; do not retreat into an AI or 'I cannot watch' disclaimer.";
|
|
155
|
-
}
|
|
156
146
|
if (/\b(?:you'?d|you\s+would|you\s+need\s+to|please)\b.*\b(?:check|look at|poke|nudge)\b.*\b(?:log|logs|dashboard|jellyfin|unmanic)\b/iu.test(answer)) {
|
|
157
147
|
return "Do not send Ari to check Jellyfin, Unmanic, dashboards, or logs for ordinary library visibility while safe catalog tools are available. Use the catalog evidence, or if the catalog read itself fails, say exactly what I could and could not verify.";
|
|
158
148
|
}
|
|
159
|
-
if (UNSOLICITED_PIVOT.test(answer))
|
|
160
|
-
return "Answer the media question that was asked, then stop; do not append an unsolicited question or recommendation pivot.";
|
|
161
149
|
if (REPORT_LABEL.test(answer))
|
|
162
150
|
return "Answer conversationally; do not use status-report labels such as version:, house:, render check:, status:, or result:.";
|
|
163
151
|
if (!technicalDetailRequested && HOUSEHOLD_JARGON.test(answer))
|
|
@@ -168,7 +156,6 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
168
156
|
// Prefer media_search when the media surface is installed: it grounds both what is
|
|
169
157
|
// on the shelf and what is worth adding. Fall back to the legacy restricted read.
|
|
170
158
|
const catalogTool = advertisedToolNames.includes(MEDIA_SEARCH_TOOL) ? MEDIA_SEARCH_TOOL : LEGACY_CATALOG_TOOL;
|
|
171
|
-
const canRequestMedia = advertisedToolNames.includes(MEDIA_REQUEST_TOOL);
|
|
172
159
|
if (!advertisedToolNames.includes(catalogTool))
|
|
173
160
|
return undefined;
|
|
174
161
|
const normalized = (0, sanctuary_media_optimization_1.normalizeSanctuaryMediaText)(request);
|
|
@@ -181,9 +168,12 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
181
168
|
if (operationalOnly || (!mentionsMedia && !requestedTitleQuery) || !asksCatalog)
|
|
182
169
|
return undefined;
|
|
183
170
|
const kind = requestKind(normalized, requestedTitleQuery);
|
|
184
|
-
const asksForAddition = /\b(?:add|missing from|must get)\b/u.test(normalized);
|
|
185
171
|
const asksForCount = /\b(?:how many|count)\b/u.test(normalized);
|
|
186
172
|
const listCount = requestedListCount(normalized);
|
|
173
|
+
// Picking something to watch is a conversation, not a catalog read. Forcing a lookup and a title into every reply threw away
|
|
174
|
+
// the greeting and the questions (2026-10-11, Rach handoff); the shelf-claim answer gate holds whatever a reply says is on the shelf.
|
|
175
|
+
if (kind === "taste_recommendation" || (kind === "other" && !listCount && !asksForCount))
|
|
176
|
+
return undefined;
|
|
187
177
|
const technicalDetailRequested = /\b(?:technical|technically|implementation|endpoint|backend|debug|detail|internals?)\b/u.test(normalized);
|
|
188
178
|
let latestEvidence;
|
|
189
179
|
const names = [catalogTool];
|
|
@@ -202,11 +192,7 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
202
192
|
? `Use ${catalogTool} before answering. Lead with the direct answer from current catalog evidence, report the shelf count in ordinary household language, and stop without sampled titles or a follow-up question.`
|
|
203
193
|
: kind === "title_lookup"
|
|
204
194
|
? `Use ${catalogTool} with the requested title before answering. Confirm its presence or absence directly from current catalog evidence.`
|
|
205
|
-
:
|
|
206
|
-
? `Use ${catalogTool} before answering. Make one concise, decisive choice grounded in returned catalog evidence; do not volunteer an AI or 'I cannot watch' disclaimer.`
|
|
207
|
-
: listCount
|
|
208
|
-
? `Use ${catalogTool} with limit ${listCount} before answering, then name exactly the returned catalog titles.`
|
|
209
|
-
: `Use ${catalogTool} before answering and base any named titles on the returned catalog evidence.`,
|
|
195
|
+
: `Use ${catalogTool} with limit ${listCount} before answering, then name exactly the returned catalog titles.`,
|
|
210
196
|
validateRequiredToolResult(name, result, args) {
|
|
211
197
|
if (name !== catalogTool)
|
|
212
198
|
return false;
|
|
@@ -224,10 +210,6 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
224
210
|
return "Search for the requested title by name before answering whether it is on the shelf.";
|
|
225
211
|
if (kind === "title_lookup" && query !== requestedTitleQuery)
|
|
226
212
|
return `Search for the requested title ${requestedTitleQuery} before answering whether it is on the shelf.`;
|
|
227
|
-
// media_search browses by genre, keyword and year and reports shelf state per
|
|
228
|
-
// title, so an addition question does not need a per-candidate title query.
|
|
229
|
-
if (asksForAddition && !query && catalogTool !== MEDIA_SEARCH_TOOL)
|
|
230
|
-
return "Search for one candidate title by name before recommending it as an addition.";
|
|
231
213
|
if (listCount && Number(args.limit) !== listCount && catalogTool !== MEDIA_SEARCH_TOOL)
|
|
232
214
|
return `Set the catalog limit to ${listCount} so the answer is grounded in exactly the requested number of titles.`;
|
|
233
215
|
return undefined;
|
|
@@ -264,26 +246,6 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
|
|
|
264
246
|
if (!includesNormalizedPhrase(answer, requestedTitleQuery))
|
|
265
247
|
return "Name the requested title in the direct answer.";
|
|
266
248
|
}
|
|
267
|
-
if (kind === "taste_recommendation" && latest) {
|
|
268
|
-
if (asksForAddition) {
|
|
269
|
-
if (latest.fromMediaSearch) {
|
|
270
|
-
// media_search states shelf membership per title, so a recommendation is
|
|
271
|
-
// grounded when the named candidate came back marked absent.
|
|
272
|
-
if (!latest.offShelfTitles.some((title) => includesNormalizedPhrase(answer, title))) {
|
|
273
|
-
return "Recommend only a title the current search returned as not on the shelf.";
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
else if (!latest.query || latest.matchedItems !== 0 || !includesNormalizedPhrase(answer, latest.query)) {
|
|
277
|
-
return "Recommend an addition only after an exact current catalog search shows that candidate is absent.";
|
|
278
|
-
}
|
|
279
|
-
if (!canRequestMedia && /\b(?:i|we)\s+(?:added|requested|submitted|queued)\b/iu.test(answer)) {
|
|
280
|
-
return "Do not claim the title was added or requested when no media-request action was available.";
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
else if (!latest.titles.some((title) => includesNormalizedPhrase(answer, title))) {
|
|
284
|
-
return "Make the choice from a title returned by the current catalog evidence.";
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
249
|
if (kind === "other" && latest) {
|
|
288
250
|
if (!latest.fromMediaSearch)
|
|
289
251
|
return catalogTitleGroundingRejection(answer, latest, listCount);
|
|
@@ -301,8 +301,8 @@ function createSanctuaryToolContext(agentName) {
|
|
|
301
301
|
});
|
|
302
302
|
return {
|
|
303
303
|
agentRoot,
|
|
304
|
-
// The Sanctuary profile opts in to the settle-time checks: replies about a work are held to what the turn looked up, and a brevity request is honored.
|
|
305
|
-
answerGates: { sourceGrounding: true, brevity: true },
|
|
304
|
+
// The Sanctuary profile opts in to the settle-time checks: replies about a work are held to what the turn looked up, "on the shelf" to what a library lookup found, and a brevity request is honored.
|
|
305
|
+
answerGates: { sourceGrounding: true, brevity: true, shelfClaims: true },
|
|
306
306
|
sanctuary: {
|
|
307
307
|
// The model-facing list carries the steward policy's desired state beside docker's: a container kept stopped on purpose is not a fault. The restart path reads the plain list.
|
|
308
308
|
listContainers: acceptanceRead("unraid_list_containers", async (signal) => (0, sanctuary_container_policy_1.annotateContainers)(signal ? await reads.listContainers(signal) : await reads.listContainers(), currentStewardPolicy(agentRoot), Date.now())),
|
|
@@ -0,0 +1,80 @@
|
|
|
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.OWNER_CHAT_HANDOFF_MS = void 0;
|
|
37
|
+
exports.ownerChatHandedOff = ownerChatHandedOff;
|
|
38
|
+
exports.readHeldNotices = readHeldNotices;
|
|
39
|
+
exports.writeHeldNotices = writeHeldNotices;
|
|
40
|
+
/**
|
|
41
|
+
* Ari sometimes hands his Telegram chat to someone else ("I'm going to hand you to my wife, Rachel"). For a while after that,
|
|
42
|
+
* the chat is that person's conversation, so house notices (an outage, a recovery) wait instead of landing in it
|
|
43
|
+
* (2026-10-11: two Jellyfin outage notes went into Rachel's movie chat). The window runs until the chat has been quiet for two
|
|
44
|
+
* hours, so a long conversation is never interrupted. Pure functions plus a small held-notice file; the Telegram sense wires them in.
|
|
45
|
+
*/
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const session_events_1 = require("../heart/session-events");
|
|
49
|
+
const runtime_1 = require("../nerves/runtime");
|
|
50
|
+
exports.OWNER_CHAT_HANDOFF_MS = 2 * 60 * 60 * 1000;
|
|
51
|
+
const HANDOFF_CUE = /\b(?:hand(?:ing)?|pass(?:ing)?)\s+you\s+(?:(?:over|off)\s+)?to\b|\bsay\s+(?:hi|hello)\s+to\s+(?:her|him|them)\b/i;
|
|
52
|
+
/** True when the owner's chat was handed to someone else and has had a message from them within the window. */
|
|
53
|
+
function ownerChatHandedOff(events, now) {
|
|
54
|
+
let lastMessageAt = null;
|
|
55
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
56
|
+
const event = events[index];
|
|
57
|
+
if (event.role !== "user")
|
|
58
|
+
continue;
|
|
59
|
+
lastMessageAt ??= Date.parse(event.time.observedAt ?? event.time.recordedAt);
|
|
60
|
+
if (HANDOFF_CUE.test((0, session_events_1.extractEventText)(event)))
|
|
61
|
+
return Number.isFinite(lastMessageAt) && now - lastMessageAt < exports.OWNER_CHAT_HANDOFF_MS;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function readHeldNotices(filePath) {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
68
|
+
return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry?.key === "string" && typeof entry?.text === "string") : [];
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function writeHeldNotices(filePath, notices) {
|
|
75
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
76
|
+
const temporary = `${filePath}.${process.pid}.tmp`;
|
|
77
|
+
fs.writeFileSync(temporary, JSON.stringify(notices), { mode: 0o600 });
|
|
78
|
+
fs.renameSync(temporary, filePath);
|
|
79
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.telegram_held_notices_saved", message: "saved the house notices held during a handoff", meta: { held: notices.length } });
|
|
80
|
+
}
|
package/dist/senses/telegram.js
CHANGED
|
@@ -85,6 +85,7 @@ const sanctuary_grounding_1 = require("./sanctuary-grounding");
|
|
|
85
85
|
const telegram_approval_runtime_1 = require("./telegram-approval-runtime");
|
|
86
86
|
const telegram_audit_ledger_1 = require("./telegram-audit-ledger");
|
|
87
87
|
const session_events_1 = require("../heart/session-events");
|
|
88
|
+
const telegram_handoff_1 = require("./telegram-handoff");
|
|
88
89
|
const router_1 = require("../heart/external-events/router");
|
|
89
90
|
const session_transaction_1 = require("../mind/session-transaction");
|
|
90
91
|
const approval_files_1 = require("../heart/approval-files");
|
|
@@ -1128,6 +1129,45 @@ function createTelegramSenseApp(options) {
|
|
|
1128
1129
|
...(onMessageDelivered ? { onMessageDelivered } : {}),
|
|
1129
1130
|
});
|
|
1130
1131
|
};
|
|
1132
|
+
// House notices wait while Ari's chat is handed to someone else, then go out with the next notice, message or reconcile pass
|
|
1133
|
+
// (see telegram-handoff). Releases run one at a time so two callers never send the same held notice twice, and a notice leaves the
|
|
1134
|
+
// held file before it is sent: from then on the effect journal owns it, exactly like a notice that was never held.
|
|
1135
|
+
const heldNoticesPath = path.join(agentRoot, "state", "telegram", "held-owner-notices.json");
|
|
1136
|
+
const ownerChatIsHandedOff = () => {
|
|
1137
|
+
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
|
|
1138
|
+
return (0, telegram_handoff_1.ownerChatHandedOff)((0, session_events_1.loadSessionEnvelopeFile)(sessionPath)?.events ?? [], Date.now());
|
|
1139
|
+
};
|
|
1140
|
+
const holdNoticeDuringHandoff = (text, key) => {
|
|
1141
|
+
if (!ownerChatIsHandedOff())
|
|
1142
|
+
return false;
|
|
1143
|
+
const held = (0, telegram_handoff_1.readHeldNotices)(heldNoticesPath);
|
|
1144
|
+
if (!held.some((notice) => notice.key === key))
|
|
1145
|
+
(0, telegram_handoff_1.writeHeldNotices)(heldNoticesPath, [...held, { key, text }]);
|
|
1146
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.telegram_notice_held_for_handoff", message: "held a house notice while the owner's chat is handed to someone else", meta: { agentName: options.agentName } });
|
|
1147
|
+
return true;
|
|
1148
|
+
};
|
|
1149
|
+
let heldNoticeRelease = Promise.resolve();
|
|
1150
|
+
const releaseHeldNotices = async (signal) => {
|
|
1151
|
+
// Nearly always nothing is held: return without queueing, so ordinary turns and the reconcile tick pay nothing.
|
|
1152
|
+
if ((0, telegram_handoff_1.readHeldNotices)(heldNoticesPath).length === 0)
|
|
1153
|
+
return;
|
|
1154
|
+
const release = heldNoticeRelease.then(async () => {
|
|
1155
|
+
const held = (0, telegram_handoff_1.readHeldNotices)(heldNoticesPath);
|
|
1156
|
+
if (ownerChatIsHandedOff())
|
|
1157
|
+
return;
|
|
1158
|
+
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
|
|
1159
|
+
for (const notice of held) {
|
|
1160
|
+
(0, telegram_handoff_1.writeHeldNotices)(heldNoticesPath, (0, telegram_handoff_1.readHeldNotices)(heldNoticesPath).filter((entry) => entry.key !== notice.key));
|
|
1161
|
+
await recordAcceptedEffects(sessionPath, [await deliverButlerEffect(notice.text, notice.key, signal)]);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
heldNoticeRelease = release.catch(() => undefined);
|
|
1165
|
+
await release;
|
|
1166
|
+
};
|
|
1167
|
+
/** For callers that must not fail because a held notice failed to send; the effect journal keeps the failed send. */
|
|
1168
|
+
const releaseHeldNoticesQuietly = () => releaseHeldNotices().catch((error) => {
|
|
1169
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.telegram_held_notice_release_error", message: "a held house notice failed to send; the effect journal has it", meta: { agentName: options.agentName, subject, error: transportError(error) } });
|
|
1170
|
+
});
|
|
1131
1171
|
recordAcceptedEffects = async (sessionPath, artifacts, bootstrapInbound, causalEventIds) => {
|
|
1132
1172
|
await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store: getEffectJournal(), sessionPath, artifacts, ...(bootstrapInbound ? { inbound: bootstrapInbound } : {}), ...(causalEventIds ? { causalEventIds } : {}) });
|
|
1133
1173
|
for (const artifact of artifacts) {
|
|
@@ -1179,6 +1219,7 @@ function createTelegramSenseApp(options) {
|
|
|
1179
1219
|
});
|
|
1180
1220
|
const failsafeScanCache = new router_1.ExternalEventScanCache();
|
|
1181
1221
|
reconcileSystemFailsafes = async () => {
|
|
1222
|
+
await releaseHeldNoticesQuietly();
|
|
1182
1223
|
if (!options.privilegedFailsafe)
|
|
1183
1224
|
return;
|
|
1184
1225
|
const target = configuredOwnerTarget();
|
|
@@ -1503,6 +1544,7 @@ function createTelegramSenseApp(options) {
|
|
|
1503
1544
|
const turnEffects = [];
|
|
1504
1545
|
let deliveryOrdinal = 0;
|
|
1505
1546
|
await recoverEffectOutbox();
|
|
1547
|
+
await releaseHeldNoticesQuietly();
|
|
1506
1548
|
let ingressRelations = { replyToEventId: null, threadRootEventId: null, references: [inboundReference] };
|
|
1507
1549
|
if (message.replyToMessageId && /^[1-9][0-9]*$/u.test(message.replyToMessageId)) {
|
|
1508
1550
|
const replyMessageId = Number(message.replyToMessageId);
|
|
@@ -1902,7 +1944,11 @@ function createTelegramSenseApp(options) {
|
|
|
1902
1944
|
});
|
|
1903
1945
|
return;
|
|
1904
1946
|
}
|
|
1905
|
-
const
|
|
1947
|
+
const key = `proactive:${(0, node_crypto_1.randomUUID)()}`;
|
|
1948
|
+
if (holdNoticeDuringHandoff(proactiveText, key))
|
|
1949
|
+
return;
|
|
1950
|
+
await releaseHeldNotices(signal);
|
|
1951
|
+
const effect = await deliverButlerEffect(proactiveText, key, signal);
|
|
1906
1952
|
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
|
|
1907
1953
|
await recordAcceptedEffects(sessionPath, [effect]);
|
|
1908
1954
|
});
|
|
@@ -1914,7 +1960,11 @@ function createTelegramSenseApp(options) {
|
|
|
1914
1960
|
throw new Error("Telegram external event identity is invalid");
|
|
1915
1961
|
await runWithAcceptanceAuditOwner(async () => {
|
|
1916
1962
|
const receiptIdentity = JSON.stringify([source, eventId, input.generation]);
|
|
1917
|
-
const
|
|
1963
|
+
const text = requiredText(input.text, "external event decision");
|
|
1964
|
+
if (holdNoticeDuringHandoff(text, `external-event:${receiptIdentity}`))
|
|
1965
|
+
return;
|
|
1966
|
+
await releaseHeldNotices(input.signal);
|
|
1967
|
+
const effect = await deliverButlerEffect(text, `external-event:${receiptIdentity}`, input.signal);
|
|
1918
1968
|
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
|
|
1919
1969
|
await recordAcceptedEffects(sessionPath, [effect]);
|
|
1920
1970
|
});
|
|
@@ -1927,7 +1977,11 @@ function createTelegramSenseApp(options) {
|
|
|
1927
1977
|
if (Buffer.byteLength(noticeId) > 512)
|
|
1928
1978
|
throw new Error("Telegram owner notice id is invalid");
|
|
1929
1979
|
await runWithAcceptanceAuditOwner(async () => {
|
|
1930
|
-
const
|
|
1980
|
+
const text = requiredText(input.text, "owner notice");
|
|
1981
|
+
if (holdNoticeDuringHandoff(text, `owner-notice:${noticeId}`))
|
|
1982
|
+
return;
|
|
1983
|
+
await releaseHeldNotices(input.signal);
|
|
1984
|
+
const effect = await deliverButlerEffect(text, `owner-notice:${noticeId}`, input.signal);
|
|
1931
1985
|
const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
|
|
1932
1986
|
await recordAcceptedEffects(sessionPath, [effect]);
|
|
1933
1987
|
});
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.905",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.905",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.905",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22"
|
|
6
6
|
},
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"test": "vitest run",
|
|
37
37
|
"test:integration": "npm run build && vitest run --config vitest.integration.config.ts",
|
|
38
38
|
"test:e2e:package": "npm run build && node scripts/package-e2e.cjs",
|
|
39
|
+
"test:e2e:self-update": "node scripts/self-update-package-e2e.cjs",
|
|
39
40
|
"test:e2e:ouro-bot-package": "node scripts/ouro-bot-package-e2e.cjs",
|
|
40
41
|
"test:e2e:real-smoke": "npm run build && node scripts/nightly-real-smoke.cjs",
|
|
41
42
|
"typecheck:mailbox-ui": "tsc --noEmit -p packages/mailbox-ui/tsconfig.json",
|