@opencoreai/opencore 0.2.2 → 0.3.0
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/opencore dashboard/app.js +74 -31
- package/opencore dashboard/styles.css +71 -32
- package/package.json +2 -2
- package/scripts/postinstall.mjs +7 -0
- package/src/credential-store.mjs +11 -0
- package/src/dashboard-server.ts +48 -3
- package/src/index.ts +382 -28
- package/src/opencore-indicator.m +493 -0
- package/templates/default-computer-profile.md +7 -0
- package/templates/default-guidelines.md +4 -0
- package/templates/default-instructions.md +2 -0
- package/src/opencore-indicator.js +0 -140
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { MacController } from "./mac-controller.mjs";
|
|
|
14
14
|
import { SKILL_CATALOG } from "./skill-catalog.mjs";
|
|
15
15
|
import {
|
|
16
16
|
buildCredentialExecutionContext,
|
|
17
|
+
credentialStorePath,
|
|
17
18
|
ensureCredentialStore,
|
|
18
19
|
extractCredentialSaveCandidates,
|
|
19
20
|
readCredentialStore,
|
|
@@ -35,6 +36,7 @@ const MANAGER_HEARTBEAT_INTERVAL_MS = Math.max(
|
|
|
35
36
|
const OPENCORE_HOME = path.join(os.homedir(), ".opencore");
|
|
36
37
|
const SOUL_PATH = path.join(OPENCORE_HOME, "soul.md");
|
|
37
38
|
const MEMORY_PATH = path.join(OPENCORE_HOME, "memory.md");
|
|
39
|
+
const COMPUTER_PROFILE_PATH = path.join(OPENCORE_HOME, "computer-profile.md");
|
|
38
40
|
const HEARTBEAT_PATH = path.join(OPENCORE_HOME, "heartbeat.md");
|
|
39
41
|
const GUIDELINES_PATH = path.join(OPENCORE_HOME, "guidelines.md");
|
|
40
42
|
const INSTRUCTIONS_PATH = path.join(OPENCORE_HOME, "instructions.md");
|
|
@@ -49,9 +51,18 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
49
51
|
const __dirname = path.dirname(__filename);
|
|
50
52
|
const ROOT_DIR = path.resolve(__dirname, "..");
|
|
51
53
|
const TEMPLATE_DIR = path.join(ROOT_DIR, "templates");
|
|
52
|
-
const
|
|
54
|
+
const INDICATOR_SOURCE_PATH = path.join(ROOT_DIR, "src", "opencore-indicator.m");
|
|
55
|
+
const INDICATOR_BINARY_PATH = path.join(OPENCORE_HOME, "cache", "opencore-indicator");
|
|
53
56
|
const DEFAULT_SOUL = "# OpenCore Soul\nName: OpenCore\n";
|
|
54
57
|
const DEFAULT_MEMORY = "# OpenCore Memory\n";
|
|
58
|
+
const DEFAULT_COMPUTER_PROFILE = `# OpenCore Computer Profile
|
|
59
|
+
|
|
60
|
+
## Machine Snapshot
|
|
61
|
+
- Pending first machine scan.
|
|
62
|
+
|
|
63
|
+
## Learned Facts
|
|
64
|
+
- None yet.
|
|
65
|
+
`;
|
|
55
66
|
const DEFAULT_HEARTBEAT = `# OpenCore Heartbeat
|
|
56
67
|
|
|
57
68
|
## Current Task
|
|
@@ -74,6 +85,7 @@ This file stores durable notes and action history for both the Manager Agent and
|
|
|
74
85
|
const EDITABLE_FILES: Record<string, string> = {
|
|
75
86
|
soul: SOUL_PATH,
|
|
76
87
|
memory: MEMORY_PATH,
|
|
88
|
+
"computer-profile": COMPUTER_PROFILE_PATH,
|
|
77
89
|
heartbeat: HEARTBEAT_PATH,
|
|
78
90
|
guidelines: GUIDELINES_PATH,
|
|
79
91
|
instructions: INSTRUCTIONS_PATH,
|
|
@@ -147,6 +159,7 @@ type TelegramConfig = {
|
|
|
147
159
|
const require = createRequire(import.meta.url);
|
|
148
160
|
const { version: APP_VERSION } = require("../package.json");
|
|
149
161
|
const execFileAsync = promisify(execFile);
|
|
162
|
+
process.umask(0o077);
|
|
150
163
|
|
|
151
164
|
async function readSettings() {
|
|
152
165
|
try {
|
|
@@ -178,6 +191,155 @@ async function ensureTemplateApplied(filePath: string, template: string, legacyD
|
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
193
|
|
|
194
|
+
async function chmodIfPossible(targetPath: string, mode: number) {
|
|
195
|
+
try {
|
|
196
|
+
await fs.chmod(targetPath, mode);
|
|
197
|
+
} catch {}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function enforceOpenCorePermissions() {
|
|
201
|
+
const dirs = [
|
|
202
|
+
OPENCORE_HOME,
|
|
203
|
+
path.join(OPENCORE_HOME, "configs"),
|
|
204
|
+
path.join(OPENCORE_HOME, "logs"),
|
|
205
|
+
path.join(OPENCORE_HOME, "cache"),
|
|
206
|
+
SCREENSHOT_DIR,
|
|
207
|
+
SKILLS_DIR,
|
|
208
|
+
];
|
|
209
|
+
const files = [
|
|
210
|
+
SOUL_PATH,
|
|
211
|
+
MEMORY_PATH,
|
|
212
|
+
COMPUTER_PROFILE_PATH,
|
|
213
|
+
HEARTBEAT_PATH,
|
|
214
|
+
GUIDELINES_PATH,
|
|
215
|
+
INSTRUCTIONS_PATH,
|
|
216
|
+
SETTINGS_PATH,
|
|
217
|
+
SCHEDULES_PATH,
|
|
218
|
+
INDICATOR_STATE_PATH,
|
|
219
|
+
SCHEDULER_LOG_PATH,
|
|
220
|
+
credentialStorePath(),
|
|
221
|
+
];
|
|
222
|
+
for (const dir of dirs) await chmodIfPossible(dir, 0o700);
|
|
223
|
+
for (const file of files) await chmodIfPossible(file, 0o600);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function upsertMarkedSection(text: string, startMarker: string, endMarker: string, body: string) {
|
|
227
|
+
const nextSection = `${startMarker}\n${body.trim()}\n${endMarker}`;
|
|
228
|
+
const pattern = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`, "m");
|
|
229
|
+
if (pattern.test(text)) {
|
|
230
|
+
return text.replace(pattern, nextSection);
|
|
231
|
+
}
|
|
232
|
+
const base = String(text || "").trimEnd();
|
|
233
|
+
return `${base}\n\n${nextSection}\n`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function safeExecOutput(file: string, args: string[] = []) {
|
|
237
|
+
try {
|
|
238
|
+
const { stdout } = await execFileAsync(file, args, { maxBuffer: 1024 * 1024 * 2 } as any);
|
|
239
|
+
return String(stdout || "").trim();
|
|
240
|
+
} catch {
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function detectDefaultBrowser() {
|
|
246
|
+
const bundleId = await safeExecOutput("osascript", ["-e", 'id of application (path to default web browser)']);
|
|
247
|
+
return bundleId || "unknown";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function collectComputerProfileSnapshot() {
|
|
251
|
+
const [productName, productVersion, buildVersion, arch, model, browserBundleId] = await Promise.all([
|
|
252
|
+
safeExecOutput("sw_vers", ["-productName"]),
|
|
253
|
+
safeExecOutput("sw_vers", ["-productVersion"]),
|
|
254
|
+
safeExecOutput("sw_vers", ["-buildVersion"]),
|
|
255
|
+
safeExecOutput("uname", ["-m"]),
|
|
256
|
+
safeExecOutput("sysctl", ["-n", "hw.model"]),
|
|
257
|
+
detectDefaultBrowser(),
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
const appNames = [];
|
|
261
|
+
for (const baseDir of ["/Applications", path.join(os.homedir(), "Applications")]) {
|
|
262
|
+
try {
|
|
263
|
+
const entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
264
|
+
for (const entry of entries) {
|
|
265
|
+
if (!entry.isDirectory()) continue;
|
|
266
|
+
if (!entry.name.toLowerCase().endsWith(".app")) continue;
|
|
267
|
+
appNames.push(entry.name.replace(/\.app$/i, ""));
|
|
268
|
+
}
|
|
269
|
+
} catch {}
|
|
270
|
+
}
|
|
271
|
+
const apps = [...new Set(appNames)].sort((a, b) => a.localeCompare(b)).slice(0, 30);
|
|
272
|
+
|
|
273
|
+
return [
|
|
274
|
+
"## Machine Snapshot",
|
|
275
|
+
`- Computer model: ${model || "unknown"}`,
|
|
276
|
+
`- Architecture: ${arch || "unknown"}`,
|
|
277
|
+
`- macOS: ${[productName, productVersion].filter(Boolean).join(" ") || "unknown"}${buildVersion ? ` (${buildVersion})` : ""}`,
|
|
278
|
+
`- Default browser bundle ID: ${browserBundleId}`,
|
|
279
|
+
`- User home: ${os.homedir()}`,
|
|
280
|
+
`- Current workspace root: ${process.cwd()}`,
|
|
281
|
+
`- OpenCore project root: ${ROOT_DIR}`,
|
|
282
|
+
`- Sample installed apps: ${apps.length ? apps.join(", ") : "none detected"}`,
|
|
283
|
+
].join("\n");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function refreshComputerProfileSnapshot() {
|
|
287
|
+
const existing = await readTextFile(COMPUTER_PROFILE_PATH, DEFAULT_COMPUTER_PROFILE);
|
|
288
|
+
const snapshot = await collectComputerProfileSnapshot();
|
|
289
|
+
const next = upsertMarkedSection(
|
|
290
|
+
existing,
|
|
291
|
+
"<!-- OPENCORE_COMPUTER_SNAPSHOT_START -->",
|
|
292
|
+
"<!-- OPENCORE_COMPUTER_SNAPSHOT_END -->",
|
|
293
|
+
snapshot,
|
|
294
|
+
);
|
|
295
|
+
await fs.writeFile(COMPUTER_PROFILE_PATH, `${next.trimEnd()}\n`, "utf8");
|
|
296
|
+
await enforceOpenCorePermissions();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function shouldStoreAsComputerFact(note: string) {
|
|
300
|
+
const text = String(note || "").toLowerCase();
|
|
301
|
+
return [
|
|
302
|
+
"browser",
|
|
303
|
+
"safari",
|
|
304
|
+
"chrome",
|
|
305
|
+
"arc",
|
|
306
|
+
"finder",
|
|
307
|
+
"application",
|
|
308
|
+
"app ",
|
|
309
|
+
"installed",
|
|
310
|
+
"mac",
|
|
311
|
+
"computer",
|
|
312
|
+
"workspace",
|
|
313
|
+
"screen recording",
|
|
314
|
+
"accessibility",
|
|
315
|
+
"permission",
|
|
316
|
+
"display",
|
|
317
|
+
"desktop",
|
|
318
|
+
"folder",
|
|
319
|
+
"directory",
|
|
320
|
+
].some((token) => text.includes(token));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function appendComputerProfileFacts(notes: string[]) {
|
|
324
|
+
const lines = Array.isArray(notes)
|
|
325
|
+
? notes.map((note) => String(note || "").trim()).filter((note) => note && shouldStoreAsComputerFact(note))
|
|
326
|
+
: [];
|
|
327
|
+
if (!lines.length) return;
|
|
328
|
+
const existing = await readTextFile(COMPUTER_PROFILE_PATH, DEFAULT_COMPUTER_PROFILE);
|
|
329
|
+
const existingLines = new Set(
|
|
330
|
+
existing
|
|
331
|
+
.split("\n")
|
|
332
|
+
.map((line) => line.trim().replace(/^- /, ""))
|
|
333
|
+
.filter(Boolean),
|
|
334
|
+
);
|
|
335
|
+
const additions = lines.filter((line) => !existingLines.has(line));
|
|
336
|
+
if (!additions.length) return;
|
|
337
|
+
const updated = existing.trimEnd().replace(/\n*$/, "");
|
|
338
|
+
const body = `${updated}\n${additions.map((line) => `- ${line}`).join("\n")}\n`;
|
|
339
|
+
await fs.writeFile(COMPUTER_PROFILE_PATH, body, "utf8");
|
|
340
|
+
await enforceOpenCorePermissions();
|
|
341
|
+
}
|
|
342
|
+
|
|
181
343
|
async function ensureOpenCoreHome() {
|
|
182
344
|
await fs.mkdir(path.join(OPENCORE_HOME, "configs"), { recursive: true });
|
|
183
345
|
await fs.mkdir(path.join(OPENCORE_HOME, "logs"), { recursive: true });
|
|
@@ -187,12 +349,14 @@ async function ensureOpenCoreHome() {
|
|
|
187
349
|
|
|
188
350
|
const soulTemplate = await readTemplate("default-soul.md", DEFAULT_SOUL);
|
|
189
351
|
const memoryTemplate = await readTemplate("default-memory.md", LEGACY_DEFAULT_MEMORY);
|
|
352
|
+
const computerProfileTemplate = await readTemplate("default-computer-profile.md", DEFAULT_COMPUTER_PROFILE);
|
|
190
353
|
const guidelinesTemplate = await readTemplate("default-guidelines.md", DEFAULT_GUIDELINES);
|
|
191
354
|
const instructionsTemplate = await readTemplate("default-instructions.md", DEFAULT_INSTRUCTIONS);
|
|
192
355
|
const heartbeatTemplate = await readTemplate("default-heartbeat.md", DEFAULT_HEARTBEAT);
|
|
193
356
|
|
|
194
357
|
await ensureTemplateApplied(SOUL_PATH, soulTemplate, [DEFAULT_SOUL]);
|
|
195
358
|
await ensureTemplateApplied(MEMORY_PATH, memoryTemplate, [LEGACY_DEFAULT_MEMORY, DEFAULT_MEMORY]);
|
|
359
|
+
await ensureTemplateApplied(COMPUTER_PROFILE_PATH, computerProfileTemplate, [DEFAULT_COMPUTER_PROFILE]);
|
|
196
360
|
await ensureTemplateApplied(HEARTBEAT_PATH, heartbeatTemplate, [DEFAULT_HEARTBEAT]);
|
|
197
361
|
await ensureTemplateApplied(GUIDELINES_PATH, guidelinesTemplate, [DEFAULT_GUIDELINES]);
|
|
198
362
|
await ensureTemplateApplied(INSTRUCTIONS_PATH, instructionsTemplate, [DEFAULT_INSTRUCTIONS]);
|
|
@@ -231,6 +395,7 @@ async function ensureOpenCoreHome() {
|
|
|
231
395
|
}
|
|
232
396
|
await ensureDefaultOpenCoreSkills();
|
|
233
397
|
await ensureCredentialStore();
|
|
398
|
+
await enforceOpenCorePermissions();
|
|
234
399
|
}
|
|
235
400
|
|
|
236
401
|
async function ensureDefaultOpenCoreSkills() {
|
|
@@ -277,6 +442,7 @@ async function readSchedules(): Promise<ScheduledTaskRecord[]> {
|
|
|
277
442
|
|
|
278
443
|
async function writeSchedules(items: ScheduledTaskRecord[]) {
|
|
279
444
|
await fs.writeFile(SCHEDULES_PATH, `${JSON.stringify(items, null, 2)}\n`, "utf8");
|
|
445
|
+
await enforceOpenCorePermissions();
|
|
280
446
|
}
|
|
281
447
|
|
|
282
448
|
async function updateScheduleRecord(id: string, apply: (item: ScheduledTaskRecord) => ScheduledTaskRecord | null) {
|
|
@@ -431,23 +597,82 @@ async function showMacNotification(title: string, message: string) {
|
|
|
431
597
|
}
|
|
432
598
|
}
|
|
433
599
|
|
|
600
|
+
const indicatorStateCache: {
|
|
601
|
+
running: boolean;
|
|
602
|
+
active: boolean;
|
|
603
|
+
message: string;
|
|
604
|
+
events: string[];
|
|
605
|
+
updated_at?: string;
|
|
606
|
+
} = {
|
|
607
|
+
running: false,
|
|
608
|
+
active: false,
|
|
609
|
+
message: "OpenCore is idle",
|
|
610
|
+
events: [],
|
|
611
|
+
};
|
|
612
|
+
|
|
434
613
|
async function writeIndicatorState(state: {
|
|
435
614
|
running: boolean;
|
|
436
615
|
active: boolean;
|
|
437
616
|
message?: string;
|
|
617
|
+
events?: string[];
|
|
438
618
|
}) {
|
|
619
|
+
indicatorStateCache.running = Boolean(state.running);
|
|
620
|
+
indicatorStateCache.active = Boolean(state.active);
|
|
621
|
+
indicatorStateCache.message = String(state.message || indicatorStateCache.message || "").trim();
|
|
622
|
+
if (Array.isArray(state.events)) {
|
|
623
|
+
indicatorStateCache.events = state.events.slice(-14);
|
|
624
|
+
}
|
|
625
|
+
indicatorStateCache.updated_at = new Date().toISOString();
|
|
439
626
|
const payload = {
|
|
440
|
-
running:
|
|
441
|
-
active:
|
|
442
|
-
message:
|
|
627
|
+
running: indicatorStateCache.running,
|
|
628
|
+
active: indicatorStateCache.active,
|
|
629
|
+
message: indicatorStateCache.message,
|
|
630
|
+
events: indicatorStateCache.events,
|
|
443
631
|
updated_at: new Date().toISOString(),
|
|
444
632
|
};
|
|
445
633
|
await fs.writeFile(INDICATOR_STATE_PATH, `${JSON.stringify(payload, null, 2)}\n`, "utf8").catch(() => {});
|
|
634
|
+
await enforceOpenCorePermissions();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async function appendIndicatorEvent(text: string, options: { active?: boolean; message?: string } = {}) {
|
|
638
|
+
const line = String(text || "").trim();
|
|
639
|
+
if (!line) return;
|
|
640
|
+
const stamp = new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
641
|
+
const events = [...indicatorStateCache.events, `${stamp} ${line}`].slice(-14);
|
|
642
|
+
await writeIndicatorState({
|
|
643
|
+
running: indicatorStateCache.running || true,
|
|
644
|
+
active: options.active ?? indicatorStateCache.active,
|
|
645
|
+
message: options.message ?? indicatorStateCache.message,
|
|
646
|
+
events,
|
|
647
|
+
});
|
|
446
648
|
}
|
|
447
649
|
|
|
448
|
-
function
|
|
650
|
+
async function ensureIndicatorBinary() {
|
|
449
651
|
try {
|
|
450
|
-
const
|
|
652
|
+
const [sourceStat, binaryStat] = await Promise.all([
|
|
653
|
+
fs.stat(INDICATOR_SOURCE_PATH),
|
|
654
|
+
fs.stat(INDICATOR_BINARY_PATH).catch(() => null),
|
|
655
|
+
]);
|
|
656
|
+
if (binaryStat && binaryStat.mtimeMs >= sourceStat.mtimeMs) {
|
|
657
|
+
return INDICATOR_BINARY_PATH;
|
|
658
|
+
}
|
|
659
|
+
await execFileAsync("clang", ["-framework", "Cocoa", "-framework", "QuartzCore", INDICATOR_SOURCE_PATH, "-o", INDICATOR_BINARY_PATH], {
|
|
660
|
+
maxBuffer: 1024 * 1024 * 8,
|
|
661
|
+
} as any);
|
|
662
|
+
await chmodIfPossible(INDICATOR_BINARY_PATH, 0o755);
|
|
663
|
+
return INDICATOR_BINARY_PATH;
|
|
664
|
+
} catch (error) {
|
|
665
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
666
|
+
console.warn(`[indicator] build failed: ${message}`);
|
|
667
|
+
return "";
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async function startIndicatorProcess() {
|
|
672
|
+
try {
|
|
673
|
+
const binaryPath = await ensureIndicatorBinary();
|
|
674
|
+
if (!binaryPath) return null;
|
|
675
|
+
const child = spawn(binaryPath, [], {
|
|
451
676
|
stdio: ["ignore", "ignore", "pipe"],
|
|
452
677
|
env: { ...process.env, OPENCORE_INDICATOR_STATE_PATH: INDICATOR_STATE_PATH },
|
|
453
678
|
});
|
|
@@ -506,6 +731,7 @@ async function writeSettingsPatch(patch: Record<string, any>) {
|
|
|
506
731
|
const current = await readSettings();
|
|
507
732
|
const next = { ...current, ...patch };
|
|
508
733
|
await fs.writeFile(SETTINGS_PATH, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
734
|
+
await enforceOpenCorePermissions();
|
|
509
735
|
return next;
|
|
510
736
|
}
|
|
511
737
|
|
|
@@ -565,6 +791,39 @@ async function sendTelegramMessage(config: TelegramConfig, text: string) {
|
|
|
565
791
|
}
|
|
566
792
|
}
|
|
567
793
|
|
|
794
|
+
async function sendTelegramPhoto(config: TelegramConfig, filePath: string, caption = "") {
|
|
795
|
+
if (!config.enabled || !config.chatId || !filePath) return;
|
|
796
|
+
const data = await fs.readFile(filePath);
|
|
797
|
+
const form = new FormData();
|
|
798
|
+
form.append("chat_id", config.chatId);
|
|
799
|
+
if (caption.trim()) form.append("caption", caption.trim().slice(0, 1024));
|
|
800
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
801
|
+
const type = ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : "image/png";
|
|
802
|
+
form.append("photo", new Blob([data], { type }), path.basename(filePath));
|
|
803
|
+
|
|
804
|
+
const controller = new AbortController();
|
|
805
|
+
const timeoutMs = 12_000;
|
|
806
|
+
const timeout = setTimeout(() => controller.abort(new Error(`Telegram API sendPhoto timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
807
|
+
try {
|
|
808
|
+
const res = await fetch(`https://api.telegram.org/bot${config.botToken}/sendPhoto`, {
|
|
809
|
+
method: "POST",
|
|
810
|
+
body: form,
|
|
811
|
+
signal: controller.signal,
|
|
812
|
+
});
|
|
813
|
+
if (!res.ok) {
|
|
814
|
+
throw new Error(`Telegram API sendPhoto failed with HTTP ${res.status}`);
|
|
815
|
+
}
|
|
816
|
+
const json = await res.json();
|
|
817
|
+
if (!json?.ok) {
|
|
818
|
+
throw new Error(String(json?.description || "Telegram API sendPhoto failed."));
|
|
819
|
+
}
|
|
820
|
+
} catch (error) {
|
|
821
|
+
throw new Error(`Telegram API sendPhoto request failed: ${formatNetworkError(error)}`);
|
|
822
|
+
} finally {
|
|
823
|
+
clearTimeout(timeout);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
568
827
|
function publicTelegramConfig(config: TelegramConfig) {
|
|
569
828
|
return {
|
|
570
829
|
enabled: config.enabled,
|
|
@@ -582,10 +841,12 @@ async function loadPromptContext() {
|
|
|
582
841
|
const guidelinesTemplate = await readTemplate("default-guidelines.md", DEFAULT_GUIDELINES);
|
|
583
842
|
const instructionsTemplate = await readTemplate("default-instructions.md", DEFAULT_INSTRUCTIONS);
|
|
584
843
|
const heartbeatTemplate = await readTemplate("default-heartbeat.md", DEFAULT_HEARTBEAT);
|
|
844
|
+
const computerProfileTemplate = await readTemplate("default-computer-profile.md", DEFAULT_COMPUTER_PROFILE);
|
|
585
845
|
const soul = await readTextFile(SOUL_PATH, soulTemplate);
|
|
586
846
|
const heartbeat = await readTextFile(HEARTBEAT_PATH, heartbeatTemplate);
|
|
587
847
|
const guidelines = await readTextFile(GUIDELINES_PATH, guidelinesTemplate);
|
|
588
848
|
const instructions = await readTextFile(INSTRUCTIONS_PATH, instructionsTemplate);
|
|
849
|
+
const computerProfile = await readTextFile(COMPUTER_PROFILE_PATH, computerProfileTemplate);
|
|
589
850
|
const config = await readSettings();
|
|
590
851
|
const credentials = await readCredentialStore();
|
|
591
852
|
const memoryExcerpt = await readMemoryExcerpt(4000);
|
|
@@ -595,6 +856,7 @@ async function loadPromptContext() {
|
|
|
595
856
|
heartbeat,
|
|
596
857
|
guidelines,
|
|
597
858
|
instructions,
|
|
859
|
+
computerProfile,
|
|
598
860
|
installedSkills: installedSkills || "(no installed skills found)",
|
|
599
861
|
config: JSON.stringify(maskConfig(config), null, 2),
|
|
600
862
|
credentialsSummary: summarizeCredentialStoreForPrompt(credentials),
|
|
@@ -624,6 +886,8 @@ async function buildComputerSystemPrompt() {
|
|
|
624
886
|
ctx.credentialsSummary,
|
|
625
887
|
"OpenCore memory excerpt (recent):",
|
|
626
888
|
ctx.memoryExcerpt,
|
|
889
|
+
"OpenCore computer profile:",
|
|
890
|
+
ctx.computerProfile,
|
|
627
891
|
].join("\n\n");
|
|
628
892
|
}
|
|
629
893
|
|
|
@@ -635,14 +899,14 @@ async function buildManagerSystemPrompt() {
|
|
|
635
899
|
"Manager Agent handles everything except direct computer-use actions.",
|
|
636
900
|
"You receive user tasks first. Decide whether to answer directly, edit local markdown files, or delegate to Computer Agent.",
|
|
637
901
|
"You also maintain durable OpenCore state. When the user states a lasting rule, preference, permission, restriction, identity update, or computer fact, update the appropriate markdown files automatically.",
|
|
638
|
-
"Use guidelines.md for safety, permission, and capability restrictions. Use instructions.md for workflow preferences and operating style. Use soul.md for OpenCore identity/personality. Use memory.md for durable learned facts
|
|
902
|
+
"Use guidelines.md for safety, permission, and capability restrictions. Use instructions.md for workflow preferences and operating style. Use soul.md for OpenCore identity/personality. Use memory.md for durable learned facts and task history. Use computer-profile.md for durable machine facts, installed apps, browser facts, workspace context, and environment inventory.",
|
|
639
903
|
"Delegate to Computer Agent when UI/computer control is needed.",
|
|
640
904
|
"Use route=local when the work can be performed locally with shell/file commands without UI control, such as file management, project inspection, text generation to files, and command-line maintenance.",
|
|
641
905
|
"Do direct answer when task is informational and no computer control is required.",
|
|
642
906
|
"Do edit_file when user asks to update any markdown/state/config file or when task requires writing files.",
|
|
643
907
|
"You may edit files and folders anywhere on the user's computer when needed, unless guidelines/instructions explicitly forbid it.",
|
|
644
908
|
"Return strict JSON only, no markdown, no explanations.",
|
|
645
|
-
'JSON schema: {"route":"direct|computer|edit_file|local","direct_answer":"...","computer_task":"...","file_target":"soul|memory|heartbeat|guidelines|instructions|/absolute/or/relative/path","edit_instructions":"...","local_task":"..."}',
|
|
909
|
+
'JSON schema: {"route":"direct|computer|edit_file|local","direct_answer":"...","computer_task":"...","file_target":"soul|memory|computer-profile|heartbeat|guidelines|instructions|/absolute/or/relative/path","edit_instructions":"...","local_task":"..."}',
|
|
646
910
|
"Follow OpenCore guidelines exactly:",
|
|
647
911
|
ctx.guidelines,
|
|
648
912
|
"Follow OpenCore user instructions exactly:",
|
|
@@ -657,6 +921,8 @@ async function buildManagerSystemPrompt() {
|
|
|
657
921
|
ctx.credentialsSummary,
|
|
658
922
|
"OpenCore memory excerpt (recent):",
|
|
659
923
|
ctx.memoryExcerpt,
|
|
924
|
+
"OpenCore computer profile:",
|
|
925
|
+
ctx.computerProfile,
|
|
660
926
|
].join("\n\n");
|
|
661
927
|
}
|
|
662
928
|
|
|
@@ -1114,6 +1380,7 @@ async function managerBuildLocalCommand({
|
|
|
1114
1380
|
"Rules:\n" +
|
|
1115
1381
|
"- Use zsh-compatible syntax.\n" +
|
|
1116
1382
|
"- Prefer safe file and project commands.\n" +
|
|
1383
|
+
"- Do not use sudo, rm -rf, eval, disk erase commands, reboot commands, or download-and-execute patterns.\n" +
|
|
1117
1384
|
"- Use absolute cwd when useful, or omit it.\n" +
|
|
1118
1385
|
"- Do not include explanations or markdown.",
|
|
1119
1386
|
maxTokens: 700,
|
|
@@ -1233,11 +1500,20 @@ async function runLocalCommandTask({
|
|
|
1233
1500
|
if (!built || !built.command) {
|
|
1234
1501
|
return "I could not build a valid local shell command for that task.";
|
|
1235
1502
|
}
|
|
1503
|
+
const commandSafetyError = getUnsafeLocalCommandReason(built.command);
|
|
1504
|
+
if (commandSafetyError) {
|
|
1505
|
+
await appendMemory(`[manager] blocked_local_command=${built.command}`);
|
|
1506
|
+
return `I refused to run that local command because it violated OpenCore security rules: ${commandSafetyError}`;
|
|
1507
|
+
}
|
|
1236
1508
|
const cwd = built.cwd
|
|
1237
1509
|
? path.isAbsolute(built.cwd)
|
|
1238
1510
|
? built.cwd
|
|
1239
1511
|
: path.resolve(os.homedir(), built.cwd)
|
|
1240
1512
|
: os.homedir();
|
|
1513
|
+
const cwdSafetyError = await getUnsafeLocalCommandCwdReason(cwd);
|
|
1514
|
+
if (cwdSafetyError) {
|
|
1515
|
+
return `I refused to run that local command because the working directory was not allowed: ${cwdSafetyError}`;
|
|
1516
|
+
}
|
|
1241
1517
|
await appendMemory(`[manager] local_command=${built.command} cwd=${cwd}`);
|
|
1242
1518
|
const { stdout, stderr } = await execFileAsync("/bin/zsh", ["-lc", built.command], {
|
|
1243
1519
|
cwd,
|
|
@@ -1248,6 +1524,44 @@ async function runLocalCommandTask({
|
|
|
1248
1524
|
return output ? `${summary}\n\n${output}`.trim() : summary;
|
|
1249
1525
|
}
|
|
1250
1526
|
|
|
1527
|
+
function getUnsafeLocalCommandReason(command: string) {
|
|
1528
|
+
const value = String(command || "").trim();
|
|
1529
|
+
if (!value) return "empty command";
|
|
1530
|
+
if (value.includes("\n")) return "multi-line commands are blocked";
|
|
1531
|
+
const checks: Array<[RegExp, string]> = [
|
|
1532
|
+
[/\bsudo\b/i, "sudo escalation is blocked"],
|
|
1533
|
+
[/\brm\s+-rf\b/i, "recursive forced deletion is blocked"],
|
|
1534
|
+
[/\bdiskutil\s+erase/i, "disk erase operations are blocked"],
|
|
1535
|
+
[/\bmkfs\b/i, "filesystem formatting is blocked"],
|
|
1536
|
+
[/\bdd\s+if=/i, "raw disk write patterns are blocked"],
|
|
1537
|
+
[/\bshutdown\b|\breboot\b/i, "system shutdown and reboot are blocked"],
|
|
1538
|
+
[/\blaunchctl\s+bootout\b/i, "launchctl bootout is blocked"],
|
|
1539
|
+
[/\bcurl\b[\s\S]*\|\s*(sh|bash|zsh)\b/i, "pipe-to-shell download execution is blocked"],
|
|
1540
|
+
[/\bwget\b[\s\S]*\|\s*(sh|bash|zsh)\b/i, "pipe-to-shell download execution is blocked"],
|
|
1541
|
+
[/\beval\b/i, "shell eval is blocked"],
|
|
1542
|
+
[/<\(/, "process substitution is blocked"],
|
|
1543
|
+
];
|
|
1544
|
+
for (const [pattern, reason] of checks) {
|
|
1545
|
+
if (pattern.test(value)) return reason;
|
|
1546
|
+
}
|
|
1547
|
+
return "";
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
async function getUnsafeLocalCommandCwdReason(cwd: string) {
|
|
1551
|
+
const resolved = path.resolve(cwd);
|
|
1552
|
+
const disallowedRoots = ["/System", "/Library", "/private", "/usr"];
|
|
1553
|
+
if (disallowedRoots.some((root) => resolved === root || resolved.startsWith(`${root}/`))) {
|
|
1554
|
+
return "system-owned directories are blocked for manager local tasks";
|
|
1555
|
+
}
|
|
1556
|
+
try {
|
|
1557
|
+
const stat = await fs.stat(resolved);
|
|
1558
|
+
if (!stat.isDirectory()) return "working directory is not a directory";
|
|
1559
|
+
} catch {
|
|
1560
|
+
return "working directory does not exist";
|
|
1561
|
+
}
|
|
1562
|
+
return "";
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1251
1565
|
function isValidCronExpression(value: string) {
|
|
1252
1566
|
return /^(\S+\s+){4}\S+$/.test(String(value || "").trim());
|
|
1253
1567
|
}
|
|
@@ -1334,7 +1648,7 @@ async function recoverScheduledTasksIfNeeded({
|
|
|
1334
1648
|
last_run_at: new Date().toISOString(),
|
|
1335
1649
|
last_error: "",
|
|
1336
1650
|
});
|
|
1337
|
-
const
|
|
1651
|
+
const result = await runManagedTask({
|
|
1338
1652
|
runtime,
|
|
1339
1653
|
controller,
|
|
1340
1654
|
userPrompt: item.task,
|
|
@@ -1349,7 +1663,7 @@ async function recoverScheduledTasksIfNeeded({
|
|
|
1349
1663
|
await setScheduleRecordState(item.id, { last_status: "done", last_error: "" });
|
|
1350
1664
|
}
|
|
1351
1665
|
results.push(`${item.id}: recovered successfully`);
|
|
1352
|
-
await appendMemory(`[schedule_recovered id=${item.id}] ${answer.slice(0, 200)}`);
|
|
1666
|
+
await appendMemory(`[schedule_recovered id=${item.id}] ${result.answer.slice(0, 200)}`);
|
|
1353
1667
|
} catch (error) {
|
|
1354
1668
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1355
1669
|
await setScheduleRecordState(item.id, { last_status: "error", last_error: msg });
|
|
@@ -1374,13 +1688,13 @@ async function managerInferPersistentUpdates({
|
|
|
1374
1688
|
userText:
|
|
1375
1689
|
`User message:\n${userPrompt}\n\n` +
|
|
1376
1690
|
"Determine whether this message contains durable instructions, restrictions, preferences, identity updates, or computer facts that should be persisted for future tasks.\n" +
|
|
1377
|
-
'Return JSON only using schema: {"memory_notes":["..."],"file_updates":[{"target":"guidelines|instructions|soul|memory","instructions":"..."}]}\n' +
|
|
1691
|
+
'Return JSON only using schema: {"memory_notes":["..."],"file_updates":[{"target":"guidelines|instructions|soul|memory|computer-profile","instructions":"..."}]}\n' +
|
|
1378
1692
|
"Rules:\n" +
|
|
1379
1693
|
"- Only include file_updates for durable information that should persist beyond the current task.\n" +
|
|
1380
1694
|
"- If the user says something cannot be done on this computer or should never be done, update guidelines.\n" +
|
|
1381
1695
|
"- If the user gives preferred behavior or workflow style, update instructions.\n" +
|
|
1382
1696
|
"- If the user changes OpenCore identity/personality, update soul.\n" +
|
|
1383
|
-
"- Store important learned facts about the computer or apps in memory_notes.\n" +
|
|
1697
|
+
"- Store important learned facts about the computer or apps in memory_notes and/or computer-profile updates.\n" +
|
|
1384
1698
|
"- If nothing durable should be saved, return empty arrays.",
|
|
1385
1699
|
maxTokens: 900,
|
|
1386
1700
|
});
|
|
@@ -1425,6 +1739,7 @@ async function applyPersistentUpdatePlan({
|
|
|
1425
1739
|
}
|
|
1426
1740
|
const notes = Array.isArray(plan.memory_notes) ? plan.memory_notes : [];
|
|
1427
1741
|
await appendLearnedFacts(notes);
|
|
1742
|
+
await appendComputerProfileFacts(notes);
|
|
1428
1743
|
}
|
|
1429
1744
|
|
|
1430
1745
|
async function managerExtractLearnedFacts({
|
|
@@ -1450,7 +1765,9 @@ async function managerExtractLearnedFacts({
|
|
|
1450
1765
|
maxTokens: 500,
|
|
1451
1766
|
});
|
|
1452
1767
|
const parsed = extractJsonObject(text);
|
|
1453
|
-
|
|
1768
|
+
const notes = Array.isArray((parsed as any)?.memory_notes) ? (parsed as any).memory_notes : [];
|
|
1769
|
+
await appendComputerProfileFacts(notes);
|
|
1770
|
+
return notes;
|
|
1454
1771
|
}
|
|
1455
1772
|
|
|
1456
1773
|
async function managerWriteHeartbeat({
|
|
@@ -1843,11 +2160,13 @@ async function runManagedTask({
|
|
|
1843
2160
|
dashboard?: DashboardServer | null;
|
|
1844
2161
|
publishToConsole?: boolean;
|
|
1845
2162
|
indicatorMessage?: string;
|
|
1846
|
-
}) {
|
|
2163
|
+
}): Promise<{ answer: string; screenshotPath?: string }> {
|
|
2164
|
+
let lastScreenshotPath = "";
|
|
1847
2165
|
await appendMemory(`[user] ${userPrompt}`);
|
|
1848
2166
|
await dashboard?.publishChat({ role: "user", source: source === "schedule" ? "manager" : source, text: userPrompt });
|
|
1849
2167
|
dashboard?.publishEvent({ type: "task_started", source: source === "schedule" ? "manager" : source });
|
|
1850
2168
|
await writeIndicatorState({ running: true, active: true, message: indicatorMessage });
|
|
2169
|
+
await appendIndicatorEvent(`Task started: ${userPrompt.slice(0, 120)}`, { active: true, message: indicatorMessage });
|
|
1851
2170
|
await showMacNotification("OpenCore", indicatorMessage);
|
|
1852
2171
|
if (publishToConsole) console.log(`Running (${source})...`);
|
|
1853
2172
|
|
|
@@ -1865,8 +2184,9 @@ async function runManagedTask({
|
|
|
1865
2184
|
await dashboard?.publishChat({ role: "assistant", source: "system", text: telegramSetup.answer });
|
|
1866
2185
|
dashboard?.publishEvent({ type: "task_completed", source: source === "schedule" ? "manager" : source });
|
|
1867
2186
|
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle" });
|
|
2187
|
+
await appendIndicatorEvent("Updated Telegram settings", { active: false, message: "OpenCore is idle" });
|
|
1868
2188
|
await showMacNotification("OpenCore", "OpenCore updated Telegram settings.");
|
|
1869
|
-
return telegramSetup.answer;
|
|
2189
|
+
return { answer: telegramSetup.answer };
|
|
1870
2190
|
}
|
|
1871
2191
|
|
|
1872
2192
|
const credentialSetup = await handleCredentialSetupRequest(userPrompt);
|
|
@@ -1875,8 +2195,9 @@ async function runManagedTask({
|
|
|
1875
2195
|
await dashboard?.publishChat({ role: "assistant", source: "system", text: credentialSetup.answer });
|
|
1876
2196
|
dashboard?.publishEvent({ type: "task_completed", source: source === "schedule" ? "manager" : source });
|
|
1877
2197
|
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle" });
|
|
2198
|
+
await appendIndicatorEvent("Updated local credentials", { active: false, message: "OpenCore is idle" });
|
|
1878
2199
|
await showMacNotification("OpenCore", "OpenCore updated local credentials.");
|
|
1879
|
-
return credentialSetup.answer;
|
|
2200
|
+
return { answer: credentialSetup.answer };
|
|
1880
2201
|
}
|
|
1881
2202
|
|
|
1882
2203
|
if (wantsHeartbeatAudit(userPrompt)) {
|
|
@@ -1892,7 +2213,8 @@ async function runManagedTask({
|
|
|
1892
2213
|
await appendMemory(`[assistant] ${answer}`);
|
|
1893
2214
|
await dashboard?.publishChat({ role: "assistant", source: "system", text: answer });
|
|
1894
2215
|
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle" });
|
|
1895
|
-
|
|
2216
|
+
await appendIndicatorEvent("Heartbeat check completed", { active: false, message: "OpenCore is idle" });
|
|
2217
|
+
return { answer };
|
|
1896
2218
|
}
|
|
1897
2219
|
|
|
1898
2220
|
const schedulePlan = await managerDetectScheduleRequest({ runtime, userPrompt });
|
|
@@ -1908,8 +2230,9 @@ async function runManagedTask({
|
|
|
1908
2230
|
await dashboard?.publishChat({ role: "assistant", source: "system", text: scheduleAnswer });
|
|
1909
2231
|
dashboard?.publishEvent({ type: "task_completed", source: source === "schedule" ? "manager" : source });
|
|
1910
2232
|
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle" });
|
|
2233
|
+
await appendIndicatorEvent("Scheduled task created", { active: false, message: "OpenCore is idle" });
|
|
1911
2234
|
await showMacNotification("OpenCore", "OpenCore scheduled the requested task.");
|
|
1912
|
-
return scheduleAnswer;
|
|
2235
|
+
return { answer: scheduleAnswer };
|
|
1913
2236
|
}
|
|
1914
2237
|
|
|
1915
2238
|
const decision = await managerDecide({ runtime, userPrompt });
|
|
@@ -1954,7 +2277,15 @@ async function runManagedTask({
|
|
|
1954
2277
|
controller,
|
|
1955
2278
|
prompt: delegatedTask,
|
|
1956
2279
|
secretTaskContext: credentialTaskContext,
|
|
1957
|
-
onEvent: (evt) =>
|
|
2280
|
+
onEvent: (evt) => {
|
|
2281
|
+
if (evt?.type === "screenshot" && evt?.path) {
|
|
2282
|
+
lastScreenshotPath = String(evt.path);
|
|
2283
|
+
void appendIndicatorEvent(`Screenshot captured · step ${evt.step || "?"}`, { active: true, message: indicatorMessage });
|
|
2284
|
+
} else if (evt?.type === "action" && evt?.action) {
|
|
2285
|
+
void appendIndicatorEvent(`Action: ${String(evt.action)}`, { active: true, message: indicatorMessage });
|
|
2286
|
+
}
|
|
2287
|
+
dashboard?.publishEvent(evt);
|
|
2288
|
+
},
|
|
1958
2289
|
});
|
|
1959
2290
|
}
|
|
1960
2291
|
|
|
@@ -1967,18 +2298,29 @@ async function runManagedTask({
|
|
|
1967
2298
|
await appendLearnedFacts(await managerExtractLearnedFacts({ runtime, userPrompt, answer }));
|
|
1968
2299
|
await clearCompletedHeartbeat();
|
|
1969
2300
|
dashboard?.publishEvent({ type: "file_updated", target: HEARTBEAT_PATH });
|
|
1970
|
-
await dashboard?.publishChat({
|
|
2301
|
+
await dashboard?.publishChat({
|
|
2302
|
+
role: "assistant",
|
|
2303
|
+
source: "system",
|
|
2304
|
+
text: answer,
|
|
2305
|
+
screenshot_path: lastScreenshotPath || undefined,
|
|
2306
|
+
});
|
|
1971
2307
|
dashboard?.publishEvent({ type: "task_completed", source: source === "schedule" ? "manager" : source });
|
|
1972
2308
|
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle" });
|
|
2309
|
+
await appendIndicatorEvent(source === "schedule" ? "Scheduled task finished" : "Task finished", {
|
|
2310
|
+
active: false,
|
|
2311
|
+
message: "OpenCore is idle",
|
|
2312
|
+
});
|
|
1973
2313
|
await showMacNotification("OpenCore", source === "schedule" ? "OpenCore finished a scheduled task." : "OpenCore finished the current task.");
|
|
1974
|
-
return answer;
|
|
2314
|
+
return { answer, screenshotPath: lastScreenshotPath || undefined };
|
|
1975
2315
|
}
|
|
1976
2316
|
|
|
1977
2317
|
async function main() {
|
|
1978
2318
|
const argv = process.argv.slice(2);
|
|
1979
2319
|
const scheduledMode = argv[0] === "--scheduled-id" ? String(argv[1] || "").trim() : "";
|
|
1980
2320
|
await ensureOpenCoreHome();
|
|
1981
|
-
await
|
|
2321
|
+
await refreshComputerProfileSnapshot();
|
|
2322
|
+
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle", events: [] });
|
|
2323
|
+
await appendIndicatorEvent("OpenCore started", { active: false, message: "OpenCore is idle" });
|
|
1982
2324
|
const settings = await readSettings();
|
|
1983
2325
|
const provider = "chatgpt";
|
|
1984
2326
|
const openaiKey = String(process.env.OPENAI_API_KEY || settings.openai_api_key || "").trim();
|
|
@@ -2000,7 +2342,7 @@ async function main() {
|
|
|
2000
2342
|
displayHeight: process.env.COMPUTER_USE_DISPLAY_HEIGHT,
|
|
2001
2343
|
});
|
|
2002
2344
|
await controller.init();
|
|
2003
|
-
let indicator: any = startIndicatorProcess();
|
|
2345
|
+
let indicator: any = await startIndicatorProcess();
|
|
2004
2346
|
|
|
2005
2347
|
if (scheduledMode) {
|
|
2006
2348
|
const schedules = await readSchedules();
|
|
@@ -2028,7 +2370,7 @@ async function main() {
|
|
|
2028
2370
|
last_error: "",
|
|
2029
2371
|
});
|
|
2030
2372
|
}
|
|
2031
|
-
const
|
|
2373
|
+
const result = await runManagedTask({
|
|
2032
2374
|
runtime,
|
|
2033
2375
|
controller,
|
|
2034
2376
|
userPrompt: schedule.task,
|
|
@@ -2043,7 +2385,7 @@ async function main() {
|
|
|
2043
2385
|
} else {
|
|
2044
2386
|
await setScheduleRecordState(schedule.id, { last_run_at: now, last_status: "done", last_error: "" });
|
|
2045
2387
|
}
|
|
2046
|
-
console.log(answer);
|
|
2388
|
+
console.log(result.answer);
|
|
2047
2389
|
await writeIndicatorState({ running: false, active: false, message: "OpenCore has stopped" });
|
|
2048
2390
|
stopIndicatorProcess(indicator);
|
|
2049
2391
|
process.exit(0);
|
|
@@ -2292,16 +2634,27 @@ async function main() {
|
|
|
2292
2634
|
const item = queue.shift();
|
|
2293
2635
|
if (!item) continue;
|
|
2294
2636
|
try {
|
|
2295
|
-
const
|
|
2637
|
+
const result = await runManagedTask({
|
|
2296
2638
|
runtime,
|
|
2297
2639
|
controller,
|
|
2298
2640
|
userPrompt: item.text,
|
|
2299
2641
|
source: item.source,
|
|
2300
2642
|
dashboard,
|
|
2301
2643
|
});
|
|
2302
|
-
console.log(`\nAssistant:\n${answer}\n`);
|
|
2644
|
+
console.log(`\nAssistant:\n${result.answer}\n`);
|
|
2303
2645
|
if (item.source === "telegram") {
|
|
2304
|
-
await sendTelegramMessage(telegramConfig, answer);
|
|
2646
|
+
await sendTelegramMessage(telegramConfig, result.answer);
|
|
2647
|
+
if (result.screenshotPath) {
|
|
2648
|
+
try {
|
|
2649
|
+
await sendTelegramPhoto(telegramConfig, result.screenshotPath, "Final OpenCore screenshot");
|
|
2650
|
+
} catch (photoError) {
|
|
2651
|
+
console.warn(
|
|
2652
|
+
`[telegram] final screenshot send failed: ${
|
|
2653
|
+
photoError instanceof Error ? photoError.message : String(photoError)
|
|
2654
|
+
}`,
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2305
2658
|
}
|
|
2306
2659
|
} catch (error) {
|
|
2307
2660
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -2310,6 +2663,7 @@ async function main() {
|
|
|
2310
2663
|
await dashboard.publishChat({ role: "error", source: "system", text: `Request failed: ${msg}` });
|
|
2311
2664
|
dashboard.publishEvent({ type: "task_failed", source: item.source });
|
|
2312
2665
|
await writeIndicatorState({ running: true, active: false, message: "OpenCore is idle" });
|
|
2666
|
+
await appendIndicatorEvent(`Task failed: ${msg}`, { active: false, message: "OpenCore is idle" });
|
|
2313
2667
|
await showMacNotification("OpenCore", "OpenCore stopped because the current task failed.");
|
|
2314
2668
|
if (item.source === "telegram") {
|
|
2315
2669
|
await sendTelegramMessage(telegramConfig, `Request failed: ${msg}`);
|