@damian87/omp 0.13.0 → 0.14.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/.github/skills/schedule/SKILL.md +27 -2
- package/.github/skills/verify-byok/SKILL.md +50 -0
- package/README.md +88 -4
- package/catalog/capabilities.json +23 -0
- package/catalog/skills-general.json +25 -0
- package/dist/src/cli.js +34 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/gateway/desktop-notify.d.ts +56 -0
- package/dist/src/gateway/desktop-notify.js +183 -0
- package/dist/src/gateway/desktop-notify.js.map +1 -0
- package/dist/src/schedule/commands.d.ts +13 -0
- package/dist/src/schedule/commands.js +24 -1
- package/dist/src/schedule/commands.js.map +1 -1
- package/dist/src/schedule/deep-link.d.ts +18 -0
- package/dist/src/schedule/deep-link.js +41 -0
- package/dist/src/schedule/deep-link.js.map +1 -0
- package/dist/src/schedule/runner.d.ts +10 -0
- package/dist/src/schedule/runner.js +36 -0
- package/dist/src/schedule/runner.js.map +1 -1
- package/dist/src/schedule/types.d.ts +16 -0
- package/docs/research/2026-06-22-schedule-desktop-notifications.md +193 -0
- package/package.json +4 -2
- package/plugin.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
3
3
|
import { getInstalledStatus, installJob, uninstallJob } from "./installer.js";
|
|
4
4
|
import { deleteJob, listJobs, readJob, writeJob } from "./job-store.js";
|
|
5
5
|
import { ensureScheduleDirs, jobFilePath, jobLockPath, resolveSchedulePaths, } from "./paths.js";
|
|
@@ -65,6 +65,8 @@ export function addScheduleJob(stateCwd, opts) {
|
|
|
65
65
|
ompBinPath: resolveOmpBinPath(),
|
|
66
66
|
active: true,
|
|
67
67
|
notifyTarget: opts.notifyTarget,
|
|
68
|
+
notifyDesktop: opts.notifyDesktop ?? false,
|
|
69
|
+
notifyOpenOmp: opts.notifyOpenOmp ?? false,
|
|
68
70
|
};
|
|
69
71
|
if (opts.dryRun) {
|
|
70
72
|
messages.push(`[dry-run] would install job "${job.id}" (cron ${job.cron}) for agent ${job.bin} in ${job.cwd}`);
|
|
@@ -116,6 +118,27 @@ export function getScheduleStatus(stateCwd, id) {
|
|
|
116
118
|
const job = readJob(jobFilePath(paths.jobsDir, id));
|
|
117
119
|
return { job, osInstalled: job ? getInstalledStatus(id, job.backend) : false };
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve a job's latest run for `omp schedule open <id>` — the by-id substitute
|
|
123
|
+
* for clicking a notification: returns the job plus the full captured log so the
|
|
124
|
+
* caller can surface the latest scan with full context. Read-only; never throws.
|
|
125
|
+
*/
|
|
126
|
+
export function openScheduleResult(stateCwd, id) {
|
|
127
|
+
const paths = resolveSchedulePaths(stateCwd);
|
|
128
|
+
const job = readJob(jobFilePath(paths.jobsDir, id));
|
|
129
|
+
if (!job)
|
|
130
|
+
return { ok: false, error: `no schedule job "${id}"` };
|
|
131
|
+
let logContent;
|
|
132
|
+
if (job.lastLogPath && existsSync(job.lastLogPath)) {
|
|
133
|
+
try {
|
|
134
|
+
logContent = readFileSync(job.lastLogPath, "utf8");
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// log unreadable — still return the job metadata
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { ok: true, job, logContent };
|
|
141
|
+
}
|
|
119
142
|
/** Run handler entry used by `omp schedule run|run-now`. Missing job → clean no-op (exit 0). */
|
|
120
143
|
export async function runScheduleById(stateCwd, id) {
|
|
121
144
|
const paths = resolveSchedulePaths(stateCwd);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/schedule/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/schedule/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEzE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAIlB,MAAM,YAAY,CAAC;AAEpB,MAAM,KAAK,GAAG,kBAAkB,CAAC;AACjC,MAAM,OAAO,GAAG,+BAA+B,CAAC,CAAC,wCAAwC;AAEzF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACpC,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1E,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;AAClC,CAAC;AAUD,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,IAAwB;IACvE,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,IAAI,CAAC,EAAE,iCAAiC,EAAE,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,mBAAmB,IAAI,CAAC,IAAI,uBAAuB,EAAE,CAAC;IAC7F,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,QAAQ,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,+CAA+C,QAAQ,EAAE,EAAE,CAAC;IACnG,CAAC;IAED,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC;IAClD,+EAA+E;IAC/E,oEAAoE;IACpE,MAAM,SAAS,GACb,IAAI,CAAC,QAAQ,KAAK,SAAS;QACzB,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE;QAChE,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS;YAC1B,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3E,MAAM,GAAG,GAAgB;QACvB,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,SAAS;QAC1B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,GAAG,EAAE,QAAQ;QACb,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,kBAAkB;QAC/C,aAAa;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS;QACT,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,SAAS;QAClB,UAAU,EAAE,iBAAiB,EAAE;QAC/B,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,KAAK;QAC1C,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,KAAK;KAC3C,CAAC;IAEF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,QAAQ,CAAC,IAAI,CAAC,gCAAgC,GAAG,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,eAAe,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/G,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED,iFAAiF;IACjF,kFAAkF;IAClF,mFAAmF;IACnF,wCAAwC;IACxC,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,IAAI,QAAQ;QAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE1D,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACzD,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,2BAA2B;IAE9E,QAAQ,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC,OAAO,UAAU,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;IAC7E,IAAI,aAAa,EAAE,CAAC;QAClB,QAAQ,CAAC,IAAI,CACX,eAAe,GAAG,CAAC,EAAE,kEAAkE,GAAG,CAAC,GAAG,8BAA8B,CAC7H,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CACX,UAAU,GAAG,CAAC,EAAE,yKAAyK,CAC1L,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC3D,CAAC;AAMD,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,WAAW,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AAClH,CAAC;AAOD,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,EAAU;IAC5D,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACxD,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,SAAS,CAAC,OAAO,CAAC,CAAC;IACnB,IAAI,CAAC;QACH,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,mBAAmB;IACrB,CAAC;IACD,uDAAuD;IACvD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAOD,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,EAAU;IAC5D,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;AACjF,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,EAAU;IAC7D,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC;IACjE,IAAI,UAA8B,CAAC;IACnC,IAAI,GAAG,CAAC,WAAW,IAAI,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC;YACH,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,iDAAiD;QACnD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;AACvC,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,EAAU;IAChE,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,sBAAsB,EAAE,uCAAuC,EAAE,CAAC;IAChG,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,KAAK,EAAE;QAC/C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC;KAC/C,CAAC,CAAC;IACH,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;AACzG,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The launcher script body: cd into the job cwd and exec an interactive omp. */
|
|
2
|
+
export declare function buildOpenOmpScript(cwd: string, ompBinPath: string): string;
|
|
3
|
+
/** Write the executable launcher into the job's log dir; return its absolute path. */
|
|
4
|
+
export declare function writeOpenOmpLauncher(logDir: string, cwd: string, ompBinPath: string): string;
|
|
5
|
+
export interface OpenTargetInput {
|
|
6
|
+
platform: NodeJS.Platform;
|
|
7
|
+
notifyOpenOmp: boolean;
|
|
8
|
+
logDir: string;
|
|
9
|
+
logPath: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
ompBinPath: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the notification's click target. The "open omp" launcher is macOS-only
|
|
15
|
+
* (it relies on `.command` files opening in Terminal); everywhere else we fall
|
|
16
|
+
* back to opening the raw run log.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveOpenTarget(input: OpenTargetInput): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-link target for a scheduled run's desktop notification.
|
|
3
|
+
*
|
|
4
|
+
* node-notifier's `open` field is opened by the OS notification daemon on click,
|
|
5
|
+
* so it survives the fire-and-exit cron process. We resolve it to one of:
|
|
6
|
+
* - the raw per-run log file (`file://<logPath>`) — the default, cross-platform; or
|
|
7
|
+
* - (macOS + notifyOpenOmp) a generated `.command` launcher that opens an
|
|
8
|
+
* interactive `omp` in the job's cwd; the SessionStart `[SCHEDULE RESULTS]`
|
|
9
|
+
* banner then surfaces the latest run — i.e. "opens omp ready to engage".
|
|
10
|
+
*/
|
|
11
|
+
import { writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
/** POSIX single-quote escaping: wrap in '…', closing/escaping any embedded quote. */
|
|
15
|
+
function shellQuote(s) {
|
|
16
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
17
|
+
}
|
|
18
|
+
/** The launcher script body: cd into the job cwd and exec an interactive omp. */
|
|
19
|
+
export function buildOpenOmpScript(cwd, ompBinPath) {
|
|
20
|
+
// `--` ends option parsing so a dash-leading cwd/bin can't be read as a flag.
|
|
21
|
+
return `#!/bin/sh\ncd -- ${shellQuote(cwd)} && exec -- ${shellQuote(ompBinPath)}\n`;
|
|
22
|
+
}
|
|
23
|
+
/** Write the executable launcher into the job's log dir; return its absolute path. */
|
|
24
|
+
export function writeOpenOmpLauncher(logDir, cwd, ompBinPath) {
|
|
25
|
+
const launcherPath = join(logDir, "open-omp.command");
|
|
26
|
+
writeFileSync(launcherPath, buildOpenOmpScript(cwd, ompBinPath), { mode: 0o755 });
|
|
27
|
+
return launcherPath;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the notification's click target. The "open omp" launcher is macOS-only
|
|
31
|
+
* (it relies on `.command` files opening in Terminal); everywhere else we fall
|
|
32
|
+
* back to opening the raw run log.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveOpenTarget(input) {
|
|
35
|
+
if (input.notifyOpenOmp && input.platform === "darwin") {
|
|
36
|
+
const launcher = writeOpenOmpLauncher(input.logDir, input.cwd, input.ompBinPath);
|
|
37
|
+
return pathToFileURL(launcher).href;
|
|
38
|
+
}
|
|
39
|
+
return pathToFileURL(input.logPath).href;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=deep-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-link.js","sourceRoot":"","sources":["../../../src/schedule/deep-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,qFAAqF;AACrF,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,UAAkB;IAChE,8EAA8E;IAC9E,OAAO,oBAAoB,UAAU,CAAC,GAAG,CAAC,eAAe,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC;AACtF,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,GAAW,EAAE,UAAkB;IAClF,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACtD,aAAa,CAAC,YAAY,EAAE,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,OAAO,YAAY,CAAC;AACtB,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB;IACtD,IAAI,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QACjF,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;AAC3C,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type SchedulePaths } from "./paths.js";
|
|
2
2
|
import { type ScheduleJob, type ScheduleRunResult } from "./types.js";
|
|
3
|
+
import type { DesktopNotifyOptions } from "../gateway/desktop-notify.js";
|
|
3
4
|
export interface RunOptions {
|
|
4
5
|
/** Called when a job has expired (TTL passed or maxRuns reached) so the caller can uninstall the OS entry. */
|
|
5
6
|
onExpire?: (job: ScheduleJob) => void;
|
|
@@ -12,6 +13,15 @@ export interface RunOptions {
|
|
|
12
13
|
ok: boolean;
|
|
13
14
|
reason?: string;
|
|
14
15
|
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Override the desktop-notify implementation (tests). Defaults to
|
|
18
|
+
* `gateway/desktop-notify.notifyDesktop`. Failures are logged to stderr only
|
|
19
|
+
* and never propagate into the job result.
|
|
20
|
+
*/
|
|
21
|
+
notifyDesktop?: (opts: DesktopNotifyOptions) => Promise<{
|
|
22
|
+
ok: boolean;
|
|
23
|
+
reason?: string;
|
|
24
|
+
}>;
|
|
15
25
|
}
|
|
16
26
|
/** Execute one scheduled run (expiry check, overlap lock, timeout, log capture, result append). */
|
|
17
27
|
export declare function runScheduledJob(job: ScheduleJob, paths: SchedulePaths, opts?: RunOptions): Promise<ScheduleRunResult>;
|
|
@@ -5,6 +5,7 @@ import { resolveCopilotBin } from "../copilot/launch.js";
|
|
|
5
5
|
import { appendRunResult, readJob, writeJob } from "./job-store.js";
|
|
6
6
|
import { acquireLock, forceReleaseStaleLock, isLockStale } from "./lock.js";
|
|
7
7
|
import { jobFilePath, jobLockPath, resultsFilePath, runLogDir } from "./paths.js";
|
|
8
|
+
import { resolveOpenTarget } from "./deep-link.js";
|
|
8
9
|
import { DEFAULT_TIMEOUT_MS } from "./types.js";
|
|
9
10
|
function isExpired(job) {
|
|
10
11
|
if (job.maxRuns !== undefined && job.runCount >= job.maxRuns)
|
|
@@ -171,6 +172,41 @@ export async function runScheduledJob(job, paths, opts = {}) {
|
|
|
171
172
|
process.stderr.write(`schedule: notify crashed for ${job.id}: ${msg}\n`);
|
|
172
173
|
}
|
|
173
174
|
}
|
|
175
|
+
// Best-effort end-of-run desktop notification. Independent of (and combinable
|
|
176
|
+
// with) the Slack post above; same failure-isolation contract. The disable
|
|
177
|
+
// kill-switch is honored up front so a disabled run writes no launcher artifact.
|
|
178
|
+
if (job.notifyDesktop && !(process.env.OMP_DISABLE_DESKTOP_NOTIFY ?? "").trim()) {
|
|
179
|
+
try {
|
|
180
|
+
const open = resolveOpenTarget({
|
|
181
|
+
platform: process.platform,
|
|
182
|
+
notifyOpenOmp: job.notifyOpenOmp ?? false,
|
|
183
|
+
logDir: runLogDir(paths.logsDir, job.id),
|
|
184
|
+
logPath: result.logPath,
|
|
185
|
+
// Open the schedule STATE ROOT (where the [SCHEDULE RESULTS] banner
|
|
186
|
+
// reads from), not job.cwd — they can differ when --cwd is set.
|
|
187
|
+
cwd: paths.cwd,
|
|
188
|
+
ompBinPath: job.ompBinPath,
|
|
189
|
+
});
|
|
190
|
+
const notifyDesktop = opts.notifyDesktop ??
|
|
191
|
+
(async (o) => {
|
|
192
|
+
const { notifyDesktop: realNotify } = await import("../gateway/desktop-notify.js");
|
|
193
|
+
const r = await realNotify(o);
|
|
194
|
+
return r.ok ? { ok: true } : { ok: false, reason: r.reason };
|
|
195
|
+
});
|
|
196
|
+
const r = await notifyDesktop({
|
|
197
|
+
title: `schedule: ${job.id}`,
|
|
198
|
+
message: `${result.status} — ${result.summary}`,
|
|
199
|
+
open,
|
|
200
|
+
});
|
|
201
|
+
if (!r.ok) {
|
|
202
|
+
process.stderr.write(`schedule: desktop notify failed for ${job.id}: ${r.reason ?? "unknown"}\n`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
207
|
+
process.stderr.write(`schedule: desktop notify crashed for ${job.id}: ${msg}\n`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
174
210
|
return result;
|
|
175
211
|
}
|
|
176
212
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/schedule/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAsB,MAAM,YAAY,CAAC;AACtG,OAAO,EAAE,kBAAkB,EAAoE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/schedule/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,6 +42,18 @@ export interface ScheduleJob {
|
|
|
42
42
|
* Notify failures never fail the job — they're logged to stderr only.
|
|
43
43
|
*/
|
|
44
44
|
notifyTarget?: string;
|
|
45
|
+
/**
|
|
46
|
+
* When true, fire a native desktop notification after each completed run
|
|
47
|
+
* (independent of, and combinable with, notifyTarget). Best-effort: a failed
|
|
48
|
+
* desktop notification is logged to stderr and never fails the job.
|
|
49
|
+
*/
|
|
50
|
+
notifyDesktop?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* When true (and on macOS), the desktop notification's click action opens an
|
|
53
|
+
* interactive `omp` session in the job's cwd instead of the raw run log. Has
|
|
54
|
+
* no effect unless notifyDesktop is also set.
|
|
55
|
+
*/
|
|
56
|
+
notifyOpenOmp?: boolean;
|
|
45
57
|
}
|
|
46
58
|
/** One line in `results/<id>.jsonl`. "Seen" state lives in the cursor, not here. */
|
|
47
59
|
export interface ScheduleRunResult {
|
|
@@ -66,4 +78,8 @@ export interface ScheduleAddOptions {
|
|
|
66
78
|
dryRun?: boolean;
|
|
67
79
|
/** See ScheduleJob.notifyTarget. */
|
|
68
80
|
notifyTarget?: string;
|
|
81
|
+
/** See ScheduleJob.notifyDesktop. */
|
|
82
|
+
notifyDesktop?: boolean;
|
|
83
|
+
/** See ScheduleJob.notifyOpenOmp. */
|
|
84
|
+
notifyOpenOmp?: boolean;
|
|
69
85
|
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-06-22T13:40:00+01:00
|
|
3
|
+
researcher: Damian Borek
|
|
4
|
+
git_commit: 4044437
|
|
5
|
+
branch: main
|
|
6
|
+
repository: oh-my-copilot
|
|
7
|
+
topic: "Native desktop notifications on scheduled-job completion (feature-request assessment)"
|
|
8
|
+
tags: [research, codebase, scheduling, cron, notifications, launchd, slack, gateway, daily-log]
|
|
9
|
+
status: complete
|
|
10
|
+
last_updated: 2026-06-22
|
|
11
|
+
last_updated_by: Damian Borek
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Research: Desktop notifications on `omp schedule` job completion
|
|
15
|
+
|
|
16
|
+
**Date**: 2026-06-22 13:40 +0100
|
|
17
|
+
**Researcher**: Damian Borek
|
|
18
|
+
**Git Commit**: 4044437
|
|
19
|
+
**Branch**: main
|
|
20
|
+
**Repository**: oh-my-copilot
|
|
21
|
+
|
|
22
|
+
## Research Question
|
|
23
|
+
|
|
24
|
+
Assess the feature request: after a scheduled `omp` job finishes, fire a **native, cross-platform (macOS-first) desktop notification** containing the job name, success/failed status, and a one-line output summary (e.g. `Dependabot: C:0 H:6 M:8 L:0 — 14 alerts`). Verify (a) the schedule system actually works, (b) whether such a notification would be a **modular / opt-in** option, and (c) how job results + full output + the daily log are wired so a notification could later deep-link back into `omp`.
|
|
25
|
+
|
|
26
|
+
> Per the user's explicit "check this feature request" ask, this document maps the
|
|
27
|
+
> current system factually **and** includes a clearly-marked feasibility section at the
|
|
28
|
+
> end. The "Detailed Findings" sections describe only what exists today.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Summary
|
|
33
|
+
|
|
34
|
+
- **The schedule system exists and is wired end-to-end.** `src/schedule/` implements a durable, OS-backed scheduler: `commands.ts` (add/list/status/remove/run-now), `runner.ts` (spawn + capture + persist), `installer.ts` + `installers/{launchd,systemd,crontab}.ts` (OS triggers), `job-store.ts` / `paths.ts` (state), plus the `/schedule` skill front-end. It is exercised by 5 test files under `test/schedule/`. This matches Option C ("hybrid JSON store + OS scheduler + locked run handler") proposed in the 2026-06-01 research doc.
|
|
35
|
+
- **A post-run notification hook already exists — but it is Slack-only.** `ScheduleJob.notifyTarget` ([types.ts:48](src/schedule/types.ts)) is an optional field; when set, `runScheduledJob` posts a one-line summary to Slack via `gateway/notify.ts` after each completed run ([runner.ts:180-196](src/schedule/runner.ts)). It is **off by default** (opt-in via `--notify-target`), validated to accept **only `slack:` targets** ([target-parser.ts:92](src/gateway/target-parser.ts), [cli.ts:1455-1462](src/cli.ts)), and failures **never break the job** (logged to stderr only).
|
|
36
|
+
- **No native desktop-notification code exists anywhere.** A scan for `osascript`, `node-notifier`, `terminal-notifier`, `notify-send`, PowerShell toast, `NSUserNotification`, `display notification` across `src/`, `scripts/`, and `package.json` returns **zero hits**. The only notification transport in the repo is the Slack REST POST in `gateway/notify.ts`.
|
|
37
|
+
- **The notification payload the request wants already exists at the call site.** At [runner.ts:187](src/schedule/runner.ts) the runner has `job.id`, `result.status` (`ok`/`error`/`timeout`/`locked`/`expired`), and `result.summary` (first ~200 chars of stdout/stderr) in hand — exactly the "name / status / one-line summary" the request asks for. Today it formats them as `[schedule] <id>: <status> (<summary>)` and sends to Slack.
|
|
38
|
+
- **Full output + results + daily-log are three distinct stores:**
|
|
39
|
+
- Per-run **full output** → `.omp/state/schedule/logs/<id>/<timestamp>.log` (the `result.logPath`).
|
|
40
|
+
- Per-run **result line** (ts, exitCode, status, summary, logPath, durationMs) → `.omp/state/schedule/results/<id>.jsonl` (append-only).
|
|
41
|
+
- **Surfacing into the next session** → the `SessionStart` hook reads unseen result lines via a byte-offset cursor and injects a `[SCHEDULE RESULTS]` banner ([scripts/lib/schedule-results.mjs](scripts/lib/schedule-results.mjs)). This is the existing "show me the latest dependabot scan" deep-link substrate.
|
|
42
|
+
- The **daily log** (`.omp/memory/daily/<date>.md`, [src/daily-log.ts](src/daily-log.ts)) is a *separate* manual mechanism (`omp daily-log add/read`); the scheduler does **not** write to it automatically.
|
|
43
|
+
|
|
44
|
+
**Bottom line for the request:** the modular, opt-in notification *seam* is already built and proven (Slack). A desktop notification is a **new transport behind the same `notifyTarget` seam**, not a new subsystem. There is currently no native-OS notification code to reuse.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Detailed Findings
|
|
49
|
+
|
|
50
|
+
### A. The schedule run lifecycle — where the notification fires
|
|
51
|
+
|
|
52
|
+
`runScheduledJob(job, paths, opts)` ([src/schedule/runner.ts:47](src/schedule/runner.ts)) executes one tick:
|
|
53
|
+
|
|
54
|
+
1. **Expiry/max-runs check** before any spawn → may persist an `expired` result and call `opts.onExpire` ([runner.ts:71-83](src/schedule/runner.ts)).
|
|
55
|
+
2. **Overlap lock** (`.omp/state/schedule/jobs/<id>.lock`) so two ticks never run the same job concurrently ([runner.ts:86-106](src/schedule/runner.ts)).
|
|
56
|
+
3. **Spawn the agent** (`spawn(bin, ["--model", m, "-p", prompt, "--allow-all-tools"?], {cwd})`), capture stdout/stderr, enforce `timeoutMs` with SIGTERM→SIGKILL escalation, write the `.log`, rotate to newest 50 ([runner.ts:108-168](src/schedule/runner.ts)).
|
|
57
|
+
4. **Persist** the result (append JSONL + update job's `lastRunAt`/`lastStatus`/`lastSummary`/`lastLogPath`) ([runner.ts:56-68, 170](src/schedule/runner.ts)).
|
|
58
|
+
5. **Release the lock**, then **best-effort notify** ([runner.ts:174-196](src/schedule/runner.ts)).
|
|
59
|
+
|
|
60
|
+
The notify block is the precise integration point for the request:
|
|
61
|
+
```ts
|
|
62
|
+
// runner.ts:180-196
|
|
63
|
+
if (job.notifyTarget) {
|
|
64
|
+
const notify = opts.notify ?? (async (text, target) => {
|
|
65
|
+
const { notify: realNotify } = await import("../gateway/notify.js");
|
|
66
|
+
const r = await realNotify({ text, target });
|
|
67
|
+
return r.ok ? { ok: true } : { ok: false, reason: `${r.code}: ${r.reason}` };
|
|
68
|
+
});
|
|
69
|
+
const summary = `[schedule] ${job.id}: ${result.status} (${result.summary})`;
|
|
70
|
+
const r = await notify(summary, job.notifyTarget);
|
|
71
|
+
if (!r.ok) process.stderr.write(`schedule: notify failed for ${job.id}: ${r.reason ?? "unknown"}\n`);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
Notes: the lock is released **before** notify so a slow transport can't make the next cron tick see `locked` ([runner.ts:171-175](src/schedule/runner.ts)); the `notify` impl is injectable via `opts.notify` for tests ([runner.ts:18](src/schedule/runner.ts)); any failure or throw is swallowed to stderr ([runner.ts:188-195](src/schedule/runner.ts)).
|
|
75
|
+
|
|
76
|
+
### B. The notification seam is modular + opt-in by construction
|
|
77
|
+
|
|
78
|
+
- **Field**: `ScheduleJob.notifyTarget?: string` ([types.ts:42-48](src/schedule/types.ts)) and `ScheduleAddOptions.notifyTarget?: string` ([types.ts:73-74](src/schedule/types.ts)). Optional → **default off**.
|
|
79
|
+
- **CLI flag**: `--notify-target` parsed at [cli.ts:1455](src/cli.ts), validated by `parseTarget` *before* the job is written ([cli.ts:1456-1462](src/cli.ts)); invalid target → `add` fails fast.
|
|
80
|
+
- **Stored verbatim** onto the job at [commands.ts:89](src/schedule/commands.ts) (`notifyTarget: opts.notifyTarget`).
|
|
81
|
+
- **Help text** already advertises it: `... [--notify-target slack:<ID>] ...` ([cli.ts:42](src/cli.ts) help block).
|
|
82
|
+
- The `/schedule` SKILL.md does **not** yet mention `--notify-target` ([.github/skills/schedule/SKILL.md](.github/skills/schedule/SKILL.md)) — the flag is wired in the CLI but not surfaced in the skill steps.
|
|
83
|
+
|
|
84
|
+
### C. Target grammar — today only `slack:` is accepted
|
|
85
|
+
|
|
86
|
+
`parseTarget(raw)` ([src/gateway/target-parser.ts:77](src/gateway/target-parser.ts)) splits `"<platform>:<ref>"` and **rejects any platform other than `slack`**:
|
|
87
|
+
```ts
|
|
88
|
+
// target-parser.ts:92-94
|
|
89
|
+
if (platform !== "slack") {
|
|
90
|
+
return { ok: false, error: `unsupported platform "${platform}"; only "slack" today` };
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
The file header explicitly notes telegram/discord/feishu are "slice 2+" and Slack is "slice 1". So a `desktop:` (or similar) scheme would currently be rejected at `add` time. `notify()` in `gateway/notify.ts` is likewise Slack-specific (POST to `chat.postMessage`, requires `SLACK_BOT_TOKEN`).
|
|
94
|
+
|
|
95
|
+
### D. Cross-platform OS scheduler — verified to target macOS first
|
|
96
|
+
|
|
97
|
+
Backend selection ([installer.ts:22-28](src/schedule/installer.ts)):
|
|
98
|
+
- `darwin` → **launchd** (the macOS-first path the request targets).
|
|
99
|
+
- else if `systemctl --user` works → **systemd**.
|
|
100
|
+
- else → **crontab**.
|
|
101
|
+
|
|
102
|
+
**launchd path** ([installers/launchd.ts](src/schedule/installers/launchd.ts)):
|
|
103
|
+
- Label `com.omp.schedule.<id>`; plist at `~/Library/LaunchAgents/com.omp.schedule.<id>.plist`.
|
|
104
|
+
- `cronToLaunchdInterval` maps simple crons to `StartInterval` (every-N-min/hour) or `StartCalendarInterval` (daily/weekly at H:M); anything richer (lists/ranges) returns `null` → **falls back to crontab** ([installer.ts:50-58](src/schedule/installer.ts)).
|
|
105
|
+
- Each tick runs: `<ompBinPath> schedule run --id <id> --root <stateRoot>` ([launchd.ts:89-98](src/schedule/installers/launchd.ts)).
|
|
106
|
+
- `ompBinPath` resolved via `OMP_BIN` env → `which omp` → `process.argv[1]` ([commands.ts:29-39](src/schedule/commands.ts)).
|
|
107
|
+
- Install = write plist + `launchctl bootout` (ignore failure) + `launchctl bootstrap gui/$UID <plist>` ([launchd.ts:115-126](src/schedule/installers/launchd.ts)); idempotent. Uninstall = bootout + delete plist ([launchd.ts:128-136](src/schedule/installers/launchd.ts)).
|
|
108
|
+
|
|
109
|
+
This confirms the OS entry invokes the same `omp schedule run` handler that contains the notify block — i.e. **desktop notifications would fire from the launchd-spawned process**, in the user's GUI session (relevant: a launchd *LaunchAgent* runs in the user's GUI domain, which is what desktop notifications require).
|
|
110
|
+
|
|
111
|
+
### E. Results, full output, and session surfacing (the deep-link substrate)
|
|
112
|
+
|
|
113
|
+
On-disk layout under `.omp/state/schedule/` ([paths.ts](src/schedule/paths.ts)):
|
|
114
|
+
- `jobs/<id>.json` — the portable source of truth (all `ScheduleJob` fields incl. `lastSummary`/`lastLogPath`).
|
|
115
|
+
- `jobs/<id>.lock` — overlap lock.
|
|
116
|
+
- `results/<id>.jsonl` — append-only `ScheduleRunResult` lines (`ts, exitCode, status, summary, logPath, durationMs`) ([job-store.ts:51-54](src/schedule/job-store.ts)).
|
|
117
|
+
- `results/<id>.offset` — byte-offset "seen" cursor ([paths.ts:41-43](src/schedule/paths.ts)).
|
|
118
|
+
- `logs/<id>/<timestamp>.log` — **full** per-run stdout/stderr (the `result.logPath`), newest 50 kept ([runner.ts:31-44, 112, 152](src/schedule/runner.ts)).
|
|
119
|
+
- `logs/<id>/<id>.launchd.{out,err}.log` — launchd's own stdio ([launchd.ts:75-76](src/schedule/installers/launchd.ts)).
|
|
120
|
+
|
|
121
|
+
Surfacing into the next agent session ([scripts/lib/schedule-results.mjs](scripts/lib/schedule-results.mjs), called from [scripts/session-start.mjs:54-55](scripts/session-start.mjs)): on `SessionStart` it scans every `results/*.jsonl` from each job's cursor, emits up to 10 lines as:
|
|
122
|
+
```
|
|
123
|
+
[SCHEDULE RESULTS]
|
|
124
|
+
- <id> @ <ts>: <status> — <summary up to 100 chars>
|
|
125
|
+
```
|
|
126
|
+
then advances the cursor so each result is shown once. This is exactly the context that lets a later "show me the latest dependabot scan" prompt resolve — the summary points at the job id, and the full log lives at the `logPath` recorded on the result/job.
|
|
127
|
+
|
|
128
|
+
### F. The daily log is separate and manual
|
|
129
|
+
|
|
130
|
+
`src/daily-log.ts` maintains `.omp/memory/daily/<YYYY-MM-DD>.md` with `## Goal` / `## Log` sections, exposed via `omp daily-log set-goal|add|read|prune` ([cli.ts:42](src/cli.ts) help). The scheduler does **not** call into it — there is no code path from `runner.ts` to `daily-log.ts`. Any "results referenced in the daily log" today happens because the agent (seeing the `[SCHEDULE RESULTS]` banner) chooses to write them there, not automatically.
|
|
131
|
+
|
|
132
|
+
### G. Test coverage of the schedule path
|
|
133
|
+
|
|
134
|
+
`test/schedule/`: `commands.test.ts`, `installer.test.ts`, `job-store.test.ts`, `lock.test.ts`, `runner.test.ts` (plus `test/scripts/session-start-schedule.test.ts` for the banner). The `runner.test.ts` exercising of the injectable `opts.notify` is what makes the notify seam unit-testable without a live transport.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Feasibility Assessment (explicit "check this feature request" ask)
|
|
139
|
+
|
|
140
|
+
**What the request maps onto, concretely:**
|
|
141
|
+
|
|
142
|
+
1. **Modular / opt-in?** Yes, natively. The `notifyTarget` field + `--notify-target` flag are already the opt-in seam (default off, validated at `add`). A desktop notification fits as **a new target scheme behind the same seam** rather than a new subsystem.
|
|
143
|
+
2. **What's missing today:**
|
|
144
|
+
- A native notification transport (macOS-first). The repo has **none** — no `osascript`/`terminal-notifier`/`node-notifier`. A new transport module (sibling to `gateway/notify.ts`) plus a dispatch decision in the runner's notify block would be required.
|
|
145
|
+
- `parseTarget` rejects all non-`slack:` platforms ([target-parser.ts:92](src/gateway/target-parser.ts)); a `desktop:`-style scheme (or a sentinel like `--notify-desktop`) would need to be accepted there or routed before `parseTarget`.
|
|
146
|
+
3. **Payload** — already available verbatim at the call site (`job.id`, `result.status`, `result.summary`) ([runner.ts:187](src/schedule/runner.ts)); the "Dependabot: C:0 H:6 M:8 L:0 — 14 alerts" line is just whatever the scheduled prompt prints, truncated to ~200 chars.
|
|
147
|
+
4. **macOS delivery context** — launchd LaunchAgents run in the user's GUI domain ([launchd.ts:111-126](src/schedule/installers/launchd.ts)), which is the domain that can post user notifications; systemd `--user` / crontab runs may not have a GUI session attached, which is the cross-platform caveat to flag.
|
|
148
|
+
5. **"Brownie points" on-click deep-link** — the substrate exists (result `summary` + `logPath` + the `[SCHEDULE RESULTS]` SessionStart banner), but there is **no** click-handler / URL-scheme / app-open wiring in the repo today; that would be net-new and OS-specific (notifications that survive to carry a click action typically need a helper like `terminal-notifier -execute`, since `osascript display notification` cannot attach a click action).
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Code References
|
|
153
|
+
|
|
154
|
+
- `src/schedule/runner.ts:180-196` — the post-run notify block (integration point).
|
|
155
|
+
- `src/schedule/runner.ts:147-164` — where `result.status` + `result.summary` (the payload) are produced.
|
|
156
|
+
- `src/schedule/types.ts:42-48,73-74` — `notifyTarget` on `ScheduleJob` / `ScheduleAddOptions` (opt-in field).
|
|
157
|
+
- `src/cli.ts:1455-1462` — `--notify-target` parse + validate in `schedule add`.
|
|
158
|
+
- `src/cli.ts:1430-1521` — full `handleScheduleCommand` dispatch.
|
|
159
|
+
- `src/schedule/commands.ts:89` — `notifyTarget` stored onto the job.
|
|
160
|
+
- `src/gateway/target-parser.ts:92-94` — `slack`-only platform gate.
|
|
161
|
+
- `src/gateway/notify.ts:74-157` — Slack REST notifier (the only transport).
|
|
162
|
+
- `src/schedule/installer.ts:22-66` — OS backend detect + install.
|
|
163
|
+
- `src/schedule/installers/launchd.ts:73-126` — macOS plist + `omp schedule run` argv + launchctl.
|
|
164
|
+
- `scripts/lib/schedule-results.mjs:37-88` — `[SCHEDULE RESULTS]` SessionStart banner + cursor.
|
|
165
|
+
- `scripts/session-start.mjs:54-55` — where the banner is injected.
|
|
166
|
+
- `src/schedule/job-store.ts` / `src/schedule/paths.ts` — results JSONL, cursor, log paths.
|
|
167
|
+
- `src/daily-log.ts` — separate manual daily log (`.omp/memory/daily/<date>.md`).
|
|
168
|
+
- `.github/skills/schedule/SKILL.md` — `/schedule` front-end (does not yet mention `--notify-target`).
|
|
169
|
+
- `test/schedule/{commands,installer,job-store,lock,runner}.test.ts` — schedule coverage.
|
|
170
|
+
|
|
171
|
+
## Architecture Documentation (patterns observed)
|
|
172
|
+
|
|
173
|
+
- **Notification seam**: optional `notifyTarget` on the job → best-effort, lock-released, failure-isolated call after persist. Transport is pluggable via `opts.notify` (tested) but production-resolves to one hardcoded Slack importer.
|
|
174
|
+
- **Target grammar**: `"<platform>:<ref>"`, single-platform (`slack`) today, explicitly designed for future "slices".
|
|
175
|
+
- **OS trigger**: per-OS backend, launchd preferred on macOS, with a crontab fallback when launchd can't express the cron; each entry re-invokes `omp schedule run --id <id> --root <dir>`.
|
|
176
|
+
- **State**: project-local `.omp/state/schedule/{jobs,results,logs}/`; append-only results + byte-cursor "seen" tracking; full output in rotated per-run `.log` files.
|
|
177
|
+
- **Surfacing**: SessionStart hook banner, not a push — the daily log is a separate, manually-driven store.
|
|
178
|
+
|
|
179
|
+
## Historical Context (from docs/research/)
|
|
180
|
+
|
|
181
|
+
- `docs/research/2026-06-01-schedule-cron-feature.md` — the original design study. Its Option C (JSON store + OS scheduler + locked run handler) is what shipped. Its **Open Questions** explicitly listed *"Output delivery: per-run log files only, or also notify … when a run finds something actionable?"* — this feature request is the direct continuation of that open question. It also flagged Windows as out-of-scope for v1.
|
|
182
|
+
|
|
183
|
+
## Related Research
|
|
184
|
+
|
|
185
|
+
- `docs/research/2026-06-01-schedule-cron-feature.md`
|
|
186
|
+
|
|
187
|
+
## Open Questions
|
|
188
|
+
|
|
189
|
+
- **Scheme vs flag**: surface desktop notifications as a `notifyTarget` scheme (e.g. `desktop:`) routed before/around `parseTarget`, or as a separate boolean flag — and can a job have *both* Slack and desktop targets at once (current field is a single string)?
|
|
190
|
+
- **Transport choice on macOS**: `osascript -e 'display notification'` (no deps, no click action) vs a bundled/optional `terminal-notifier` (supports `-execute` click actions, the "brownie points" path) vs `node-notifier` (a new dependency).
|
|
191
|
+
- **Cross-platform delivery context**: systemd `--user` and crontab ticks may run without an attached GUI session; does the feature degrade gracefully (skip + stderr) off-macOS?
|
|
192
|
+
- **Deep-link target**: on click, what does "opens omp and shows the latest scan" invoke — a tmux send-keys into a running `omp` session, a fresh `omp -p "show me the latest <id> result"`, or just opening the `logPath`?
|
|
193
|
+
- **SKILL.md surfacing**: `--notify-target` is wired in the CLI but absent from the `/schedule` skill steps; would a desktop option need the skill updated too?
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@damian87/omp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Multi-agent orchestration for GitHub Copilot CLI — autonomous loops (Autopilot, Ralph, UltraQA, Ultrawork), parallel tmux agent teams, a weighted-consensus model council, a Slack chat bridge, durable scheduled jobs, and in-session skills + custom agents. Zero learning curve.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^22.15.29",
|
|
61
|
+
"@types/node-notifier": "^8.0.5",
|
|
61
62
|
"@vitest/coverage-v8": "^3.2.4",
|
|
62
63
|
"tsx": "^4.19.4",
|
|
63
64
|
"typescript": "^5.8.3",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
},
|
|
66
67
|
"dependencies": {
|
|
67
68
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
68
|
-
"@slack/bolt": "^4.4.0"
|
|
69
|
+
"@slack/bolt": "^4.4.0",
|
|
70
|
+
"node-notifier": "^10.0.1"
|
|
69
71
|
}
|
|
70
72
|
}
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-copilot",
|
|
3
3
|
"description": "Multi-agent orchestration skills for GitHub Copilot CLI — autopilot, ralph, ultrawork, ultraqa, team, council, code-review and more as in-session slash skills + custom agents.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Damian Borek",
|
|
7
7
|
"email": "borekdamian@yahoo.pl"
|