@skrr-ai/cli 0.1.102 → 0.1.104
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/dist/base-command.js +24 -0
- package/dist/commands/agent/message/send.js +6 -2
- package/dist/commands/agents/triggers/create.js +7 -17
- package/dist/commands/agents/triggers/update.js +41 -28
- package/dist/commands/commitments/create.js +2 -0
- package/dist/commands/commitments/preflight.js +18 -1
- package/dist/commands/commitments/roster.d.ts +15 -0
- package/dist/commands/commitments/roster.js +39 -0
- package/dist/commands/commitments/show.js +1 -1
- package/dist/commands/commitments/status.d.ts +17 -0
- package/dist/commands/commitments/status.js +41 -0
- package/dist/commands/commitments/update.js +12 -0
- package/dist/commands/daemon/logs.d.ts +106 -1
- package/dist/commands/daemon/logs.js +311 -15
- package/dist/commands/followups/checkin.js +46 -3
- package/dist/commands/tasks/approvals/resolve.d.ts +12 -0
- package/dist/commands/tasks/approvals/resolve.js +21 -5
- package/dist/commands/triggers/create.js +8 -17
- package/dist/commands/triggers/fire.js +3 -0
- package/dist/commands/triggers/update.js +41 -27
- package/dist/commands/widgets/check.d.ts +39 -0
- package/dist/commands/widgets/check.js +179 -0
- package/dist/lib/agent-mailbox.d.ts +0 -1
- package/dist/lib/agent-mailbox.js +0 -1
- package/dist/lib/commitment-loop-status.d.ts +16 -0
- package/dist/lib/commitment-loop-status.js +164 -0
- package/dist/lib/commitments.d.ts +28 -1
- package/dist/lib/commitments.js +184 -5
- package/dist/lib/delegated-cli.d.ts +23 -1
- package/dist/lib/delegated-cli.js +61 -4
- package/dist/lib/first-party-harness.d.ts +1 -1
- package/dist/lib/first-party-harness.js +1 -1
- package/dist/lib/session-task-write.d.ts +13 -0
- package/dist/lib/session-task-write.js +63 -3
- package/dist/lib/triggers.d.ts +26 -0
- package/dist/lib/triggers.js +134 -1
- package/dist/lib/widgetCheck.d.ts +123 -0
- package/dist/lib/widgetCheck.js +998 -0
- package/dist/lib/widgetStaticScan.d.ts +111 -0
- package/dist/lib/widgetStaticScan.js +833 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.d.ts +61 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +177 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.d.ts +61 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +171 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +11908 -8693
- package/oclif.manifest.json +219 -6
- package/package.json +2 -2
|
@@ -3,10 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = void 0;
|
|
6
7
|
exports.describeLogAge = describeLogAge;
|
|
7
8
|
exports.resolveLegacyDaemonLogPath = resolveLegacyDaemonLogPath;
|
|
9
|
+
exports.resolveDaemonLogsProfile = resolveDaemonLogsProfile;
|
|
10
|
+
exports.installedServiceLogDestinations = installedServiceLogDestinations;
|
|
11
|
+
exports.installedServiceLogCandidates = installedServiceLogCandidates;
|
|
12
|
+
exports.chooseLegacyFallbackLog = chooseLegacyFallbackLog;
|
|
13
|
+
exports.resolveForwardedLogPath = resolveForwardedLogPath;
|
|
14
|
+
exports.parseRuntimeVersion = parseRuntimeVersion;
|
|
15
|
+
exports.runtimeVersionAtLeast = runtimeVersionAtLeast;
|
|
16
|
+
exports.probeRuntimeVersion = probeRuntimeVersion;
|
|
17
|
+
exports.probeStatusLogPath = probeStatusLogPath;
|
|
18
|
+
exports.planDaemonLogs = planDaemonLogs;
|
|
8
19
|
exports.readDaemonLogTail = readDaemonLogTail;
|
|
9
|
-
exports.
|
|
20
|
+
exports.buildDaemonLogsArgs = buildDaemonLogsArgs;
|
|
10
21
|
const core_1 = require("@oclif/core");
|
|
11
22
|
const node_fs_1 = require("node:fs");
|
|
12
23
|
const node_os_1 = require("node:os");
|
|
@@ -62,6 +73,11 @@ function describeLogAge(mtimeMs, now = Date.now()) {
|
|
|
62
73
|
};
|
|
63
74
|
}
|
|
64
75
|
function resolveLegacyDaemonLogPath(flags, options = {}) {
|
|
76
|
+
const home = options.home ?? (0, node_os_1.homedir)();
|
|
77
|
+
return node_path_1.default.join(home, '.skrr', 'profiles', resolveDaemonLogsProfile(flags, options), 'logs', flags.stderr ? 'daemon.err.log' : 'daemon.log');
|
|
78
|
+
}
|
|
79
|
+
/** The profile `logs` addresses: --profile, then the profile environment variable, then the pin. */
|
|
80
|
+
function resolveDaemonLogsProfile(flags, options = {}) {
|
|
65
81
|
const home = options.home ?? (0, node_os_1.homedir)();
|
|
66
82
|
const env = options.env ?? process.env;
|
|
67
83
|
// An explicit --profile outranks the environment, as it does everywhere else.
|
|
@@ -81,7 +97,259 @@ function resolveLegacyDaemonLogPath(flags, options = {}) {
|
|
|
81
97
|
// An absent/corrupt sticky selector must match the daemon's default.
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
|
-
return
|
|
100
|
+
return profile ?? 'default';
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Read the installed launchd definition's log destinations (OSK-15993). The
|
|
104
|
+
* desktop supervisor starts the daemon with
|
|
105
|
+
* `--log-file ~/Library/Logs/Skrr/daemon.app.log` and redirects stderr to
|
|
106
|
+
* `daemon.stderr.log`, so the profile's `logs/` directory can hold a file
|
|
107
|
+
* nobody has written for a week. Read-only: the plist is parsed, never loaded.
|
|
108
|
+
*/
|
|
109
|
+
function installedServiceLogDestinations(profile, options = {}) {
|
|
110
|
+
if ((options.platform ?? process.platform) !== 'darwin')
|
|
111
|
+
return null;
|
|
112
|
+
const plist = node_path_1.default.join(options.home ?? (0, node_os_1.homedir)(), 'Library', 'LaunchAgents', `ai.skrr.daemon.${profile}.plist`);
|
|
113
|
+
let xml;
|
|
114
|
+
try {
|
|
115
|
+
xml = (0, node_fs_1.readFileSync)(plist, 'utf8');
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const unescape = (v) => v
|
|
121
|
+
.replace(/</g, '<')
|
|
122
|
+
.replace(/>/g, '>')
|
|
123
|
+
.replace(/"/g, '"')
|
|
124
|
+
.replace(/'/g, "'")
|
|
125
|
+
.replace(/&/g, '&');
|
|
126
|
+
const stringFor = (key) => {
|
|
127
|
+
const m = new RegExp(`<key>\\s*${key}\\s*</key>\\s*<string>([\\s\\S]*?)</string>`).exec(xml);
|
|
128
|
+
return m?.[1]?.trim() ? unescape(m[1].trim()) : null;
|
|
129
|
+
};
|
|
130
|
+
let logFile = null;
|
|
131
|
+
const array = /<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(xml);
|
|
132
|
+
if (array?.[1]) {
|
|
133
|
+
const args = [...array[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => unescape(m[1].trim()));
|
|
134
|
+
const at = args.lastIndexOf('--log-file');
|
|
135
|
+
if (at >= 0 && args[at + 1] && !args[at + 1].startsWith('-'))
|
|
136
|
+
logFile = args[at + 1];
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
logFile,
|
|
140
|
+
stdoutPath: stringFor('StandardOutPath'),
|
|
141
|
+
stderrPath: stringFor('StandardErrorPath'),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** The installed service's log files for the main log, or for `--stderr`. */
|
|
145
|
+
function installedServiceLogCandidates(profile, stderr, options = {}) {
|
|
146
|
+
const dest = installedServiceLogDestinations(profile, options);
|
|
147
|
+
if (!dest)
|
|
148
|
+
return [];
|
|
149
|
+
if (stderr)
|
|
150
|
+
return dest.stderrPath ? [dest.stderrPath] : [];
|
|
151
|
+
return [dest.logFile, dest.stdoutPath].filter((p) => Boolean(p));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Pick the file the legacy fallback reads. An old daemon cannot say which file
|
|
155
|
+
* it writes, so the most recently written of the profile log and the installed
|
|
156
|
+
* service's logs is the best evidence of the live one — and whenever that is
|
|
157
|
+
* not the profile log, the choice is said out loud rather than made silently.
|
|
158
|
+
*/
|
|
159
|
+
function chooseLegacyFallbackLog(args) {
|
|
160
|
+
const profileMtime = args.mtimeOf(args.profilePath);
|
|
161
|
+
let best = null;
|
|
162
|
+
for (const file of args.serviceCandidates) {
|
|
163
|
+
if (node_path_1.default.resolve(file) === node_path_1.default.resolve(args.profilePath))
|
|
164
|
+
continue;
|
|
165
|
+
const mtime = args.mtimeOf(file);
|
|
166
|
+
if (mtime === null)
|
|
167
|
+
continue;
|
|
168
|
+
if (!best || mtime > best.mtime)
|
|
169
|
+
best = { file, mtime };
|
|
170
|
+
}
|
|
171
|
+
if (!best || (profileMtime !== null && profileMtime >= best.mtime)) {
|
|
172
|
+
return { path: args.profilePath, note: null };
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
path: best.file,
|
|
176
|
+
note: `Showing ${best.file}, the installed service's log: it was written more recently than ` +
|
|
177
|
+
(profileMtime === null
|
|
178
|
+
? `the profile log ${args.profilePath}, which does not exist.`
|
|
179
|
+
: `the profile log ${args.profilePath}, which has not been written since.`) +
|
|
180
|
+
` ${args.reason ?? 'This daemon predates `logs`, so it cannot name its live log itself.'}`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Ask the installed runtime which file `logs` will read — the same live-aware
|
|
185
|
+
* answer it uses itself — so the age banner describes the file actually shown.
|
|
186
|
+
* Null when the runtime cannot answer in time; the banner is then skipped
|
|
187
|
+
* rather than printed about a file that may not be the one shown.
|
|
188
|
+
*/
|
|
189
|
+
async function resolveForwardedLogPath(flags, capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
190
|
+
try {
|
|
191
|
+
const out = await capture(buildDaemonLogsArgs({ ...flags, path: true }), {
|
|
192
|
+
timeoutMs: 5_000,
|
|
193
|
+
});
|
|
194
|
+
if (out.code !== 0)
|
|
195
|
+
return null;
|
|
196
|
+
const lines = out.stdout
|
|
197
|
+
.split('\n')
|
|
198
|
+
.map((l) => l.trim())
|
|
199
|
+
.filter(Boolean);
|
|
200
|
+
const last = lines.at(-1);
|
|
201
|
+
return last && node_path_1.default.isAbsolute(last) ? last : null;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The first runtime whose own `logs` reads the LIVE daemon's `--log-file`
|
|
209
|
+
* (OSK-15993, `daemon/src/daemon-log-target.ts`). 0.8.112 through 0.8.118 have
|
|
210
|
+
* a `logs` subcommand, so the subcommand probe says "supported", but it reads
|
|
211
|
+
* the profile's `logs/daemon.log` unconditionally — with a desktop-managed
|
|
212
|
+
* daemon, a file nobody has written for a week. Forwarding to one of those is
|
|
213
|
+
* forwarding to the bug, so the CLI forwards only at or above this version.
|
|
214
|
+
*/
|
|
215
|
+
exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = '0.8.119';
|
|
216
|
+
/** `x.y.z` from a version string, or null. A prerelease suffix is ignored. */
|
|
217
|
+
function parseRuntimeVersion(text) {
|
|
218
|
+
const m = /(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
219
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
220
|
+
}
|
|
221
|
+
/** Whether `version` is at least `floor`; null when either is not a version. */
|
|
222
|
+
function runtimeVersionAtLeast(version, floor) {
|
|
223
|
+
const a = parseRuntimeVersion(version);
|
|
224
|
+
const b = parseRuntimeVersion(floor);
|
|
225
|
+
if (!a || !b)
|
|
226
|
+
return null;
|
|
227
|
+
for (let i = 0; i < 3; i += 1) {
|
|
228
|
+
if (a[i] !== b[i])
|
|
229
|
+
return a[i] > b[i];
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
/** The installed runtime's `--version`, or null when it cannot say in time. */
|
|
234
|
+
async function probeRuntimeVersion(capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
235
|
+
try {
|
|
236
|
+
const out = await capture(['--version'], { timeoutMs: 5_000 });
|
|
237
|
+
if (out.code !== 0)
|
|
238
|
+
return null;
|
|
239
|
+
const parsed = parseRuntimeVersion(out.stdout);
|
|
240
|
+
return parsed ? parsed.join('.') : null;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The live daemon's log as the runtime's `status --json` reports it
|
|
248
|
+
* (`service.logs`). Status has named the live `--log-file` since before `logs`
|
|
249
|
+
* did, so on 0.8.117 status is right while logs is wrong. Null when status
|
|
250
|
+
* cannot answer, or answers with anything but an absolute path.
|
|
251
|
+
*/
|
|
252
|
+
async function probeStatusLogPath(flags, capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
253
|
+
try {
|
|
254
|
+
const out = await capture([...(flags.profile ? ['--profile', flags.profile] : []), 'status', '--json'], { timeoutMs: 15_000 });
|
|
255
|
+
// `status` exits non-zero when it reports problems; the JSON is still the answer.
|
|
256
|
+
const start = out.stdout.indexOf('{');
|
|
257
|
+
if (start < 0)
|
|
258
|
+
return null;
|
|
259
|
+
const parsed = JSON.parse(out.stdout.slice(start));
|
|
260
|
+
const logs = parsed?.service?.logs;
|
|
261
|
+
return typeof logs === 'string' && node_path_1.default.isAbsolute(logs) ? logs : null;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function sameLogFile(a, b) {
|
|
268
|
+
return Boolean(a && b && node_path_1.default.resolve(a) === node_path_1.default.resolve(b));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Decide which runtime, if any, `skrr daemon logs` may trust (OSK-15993).
|
|
272
|
+
*
|
|
273
|
+
* - No `logs` subcommand: the legacy fallback, as before.
|
|
274
|
+
* - A runtime at or above {@link LIVE_LOG_RESOLUTION_RUNTIME_VERSION}: forward.
|
|
275
|
+
* - An older runtime, or one whose version cannot be read: ask its `status`
|
|
276
|
+
* which file the live daemon writes and read that file here. When status
|
|
277
|
+
* cannot answer, an older runtime gets the legacy fallback (the newest of the
|
|
278
|
+
* profile log and the installed service's logs); an unknown one is forwarded.
|
|
279
|
+
*/
|
|
280
|
+
async function planDaemonLogs(flags, deps) {
|
|
281
|
+
const legacy = (reason) => {
|
|
282
|
+
const chosen = chooseLegacyFallbackLog({
|
|
283
|
+
profilePath: deps.profilePath,
|
|
284
|
+
serviceCandidates: (flags.stderr
|
|
285
|
+
? [deps.service?.stderrPath]
|
|
286
|
+
: [deps.service?.logFile, deps.service?.stdoutPath]).filter((p) => Boolean(p)),
|
|
287
|
+
mtimeOf: deps.mtimeOf,
|
|
288
|
+
reason,
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
kind: 'read',
|
|
292
|
+
path: chosen.path,
|
|
293
|
+
note: chosen.note,
|
|
294
|
+
missingHint: 'The installed daemon predates `oversky logs`; start it or upgrade it, then retry.',
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
const supports = await deps.supportsLogs();
|
|
298
|
+
if (supports === false) {
|
|
299
|
+
return legacy('This daemon predates `logs`, so it cannot name its live log itself.');
|
|
300
|
+
}
|
|
301
|
+
// Help could not be read: nothing to go on, keep the old behaviour.
|
|
302
|
+
if (supports === null)
|
|
303
|
+
return { kind: 'forward' };
|
|
304
|
+
const version = await deps.runtimeVersion();
|
|
305
|
+
const current = version
|
|
306
|
+
? runtimeVersionAtLeast(version, exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION)
|
|
307
|
+
: null;
|
|
308
|
+
if (current === true)
|
|
309
|
+
return { kind: 'forward' };
|
|
310
|
+
const live = await deps.statusLogPath();
|
|
311
|
+
const predates = version !== null
|
|
312
|
+
? `The installed runtime (${version}) predates \`logs\` following the live daemon's ` +
|
|
313
|
+
`--log-file (${exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION}), so it would read ${deps.profilePath}.`
|
|
314
|
+
: `The installed runtime did not report its version, so its \`logs\` may read ` +
|
|
315
|
+
`${deps.profilePath} instead of the live log.`;
|
|
316
|
+
if (!live) {
|
|
317
|
+
if (current === null)
|
|
318
|
+
return { kind: 'forward' };
|
|
319
|
+
return legacy(`${predates} Its \`status\` did not name the live log either.`);
|
|
320
|
+
}
|
|
321
|
+
// The live daemon writes the profile log itself: the runtime's answer is right.
|
|
322
|
+
if (sameLogFile(live, node_path_1.default.join(node_path_1.default.dirname(deps.profilePath), 'daemon.log'))) {
|
|
323
|
+
return { kind: 'forward' };
|
|
324
|
+
}
|
|
325
|
+
const missingHint = 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.';
|
|
326
|
+
if (!flags.stderr) {
|
|
327
|
+
return {
|
|
328
|
+
kind: 'read',
|
|
329
|
+
path: live,
|
|
330
|
+
note: `Showing ${live}, the live daemon's log as \`skrrd status\` reports it. ${predates}`,
|
|
331
|
+
missingHint,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// --stderr: trust the installed definition's redirect only when it is the
|
|
335
|
+
// one that launched this daemon (its --log-file is the live one) — the same
|
|
336
|
+
// rule the runtime's own resolution applies.
|
|
337
|
+
if (deps.service?.stderrPath && sameLogFile(deps.service.logFile, live)) {
|
|
338
|
+
return {
|
|
339
|
+
kind: 'read',
|
|
340
|
+
path: deps.service.stderrPath,
|
|
341
|
+
note: `Showing ${deps.service.stderrPath}, the live daemon's stderr as its service definition ` +
|
|
342
|
+
`redirects it; its full log, WARN and ERROR included, is ${live}. ${predates}`,
|
|
343
|
+
missingHint,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
kind: 'read',
|
|
348
|
+
path: live,
|
|
349
|
+
note: `Showing ${live}, the live daemon's log as \`skrrd status\` reports it; it carries every ` +
|
|
350
|
+
`WARN and ERROR line. ${predates}`,
|
|
351
|
+
missingHint,
|
|
352
|
+
};
|
|
85
353
|
}
|
|
86
354
|
/** Read a bounded trailing window, so an unrotated legacy err log is safe. */
|
|
87
355
|
function readDaemonLogTail(filePath, maxLines) {
|
|
@@ -114,7 +382,7 @@ function readDaemonLogTail(filePath, maxLines) {
|
|
|
114
382
|
* `completeTaskDurably`. Every flag declared on the command must appear here,
|
|
115
383
|
* or it parses successfully and is then silently dropped.
|
|
116
384
|
*/
|
|
117
|
-
function
|
|
385
|
+
function buildDaemonLogsArgs(flags) {
|
|
118
386
|
return [
|
|
119
387
|
// The runtime's own global option, placed before the subcommand.
|
|
120
388
|
...(flags.profile ? ['--profile', flags.profile] : []),
|
|
@@ -174,33 +442,57 @@ class DaemonLogs extends core_1.Command {
|
|
|
174
442
|
];
|
|
175
443
|
async run() {
|
|
176
444
|
const { flags } = await this.parse(DaemonLogs);
|
|
177
|
-
const args =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
445
|
+
const args = buildDaemonLogsArgs(flags);
|
|
446
|
+
const showAge = (filePath) => {
|
|
447
|
+
// Before ANY log content, on stderr so it never pollutes a pipe. `--path`
|
|
448
|
+
// is exempt: its whole contract is one path on stdout.
|
|
449
|
+
if (flags.path || !filePath)
|
|
450
|
+
return;
|
|
181
451
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const { line } = describeLogAge((0, node_fs_1.statSync)(candidate).mtimeMs);
|
|
185
|
-
this.logToStderr(line);
|
|
452
|
+
if ((0, node_fs_1.existsSync)(filePath)) {
|
|
453
|
+
this.logToStderr(`${filePath}: ${describeLogAge((0, node_fs_1.statSync)(filePath).mtimeMs).line}`);
|
|
186
454
|
}
|
|
187
455
|
}
|
|
188
456
|
catch {
|
|
189
457
|
// Never let the banner stop the logs from being shown.
|
|
190
458
|
}
|
|
191
|
-
}
|
|
459
|
+
};
|
|
192
460
|
try {
|
|
193
461
|
// `oversky 0.2.x` has profiles and writes the same files, but has no
|
|
194
462
|
// `logs` subcommand. Probe top-level help first so forwarding does not
|
|
195
463
|
// print an unrelated "unknown option" error before the useful output.
|
|
196
|
-
|
|
197
|
-
|
|
464
|
+
//
|
|
465
|
+
// OSK-15993: a runtime that HAS `logs` is not necessarily one to trust
|
|
466
|
+
// with it — 0.8.112–0.8.118 read the profile log even when the live
|
|
467
|
+
// daemon writes elsewhere. `planDaemonLogs` forwards only to a runtime
|
|
468
|
+
// that resolves the live log itself.
|
|
469
|
+
const plan = await planDaemonLogs(flags, {
|
|
470
|
+
supportsLogs: () => (0, exec_runtime_binary_1.runtimeBinarySupportsSubcommand)('logs'),
|
|
471
|
+
runtimeVersion: () => probeRuntimeVersion(),
|
|
472
|
+
statusLogPath: () => probeStatusLogPath(flags),
|
|
473
|
+
profilePath: resolveLegacyDaemonLogPath(flags),
|
|
474
|
+
service: installedServiceLogDestinations(resolveDaemonLogsProfile(flags)),
|
|
475
|
+
mtimeOf: (filePath) => {
|
|
476
|
+
try {
|
|
477
|
+
return (0, node_fs_1.statSync)(filePath).mtimeMs;
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
if (plan.kind === 'read') {
|
|
485
|
+
const logPath = plan.path;
|
|
486
|
+
// `--path` prints the one path and nothing else, on either stream.
|
|
198
487
|
if (flags.path) {
|
|
199
488
|
this.log(logPath);
|
|
200
489
|
return;
|
|
201
490
|
}
|
|
491
|
+
if (plan.note)
|
|
492
|
+
this.logToStderr(plan.note);
|
|
493
|
+
showAge(logPath);
|
|
202
494
|
if (!(0, node_fs_1.existsSync)(logPath)) {
|
|
203
|
-
this.error(`No daemon log at ${logPath}.
|
|
495
|
+
this.error(`No daemon log at ${logPath}. ${plan.missingHint}`, { exit: 1 });
|
|
204
496
|
}
|
|
205
497
|
const lines = readDaemonLogTail(logPath, flags.lines ?? 200);
|
|
206
498
|
if (lines.length > 0)
|
|
@@ -234,6 +526,10 @@ class DaemonLogs extends core_1.Command {
|
|
|
234
526
|
}
|
|
235
527
|
return;
|
|
236
528
|
}
|
|
529
|
+
// The banner must describe the file the runtime will actually print —
|
|
530
|
+
// the live daemon's log (OSK-15993) — not the profile default.
|
|
531
|
+
if (!flags.path)
|
|
532
|
+
showAge(await resolveForwardedLogPath(flags));
|
|
237
533
|
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(args);
|
|
238
534
|
this.exit(code);
|
|
239
535
|
}
|
|
@@ -99,18 +99,32 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
99
99
|
this.handleApiError(err);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
const producer = agent.boundToSession ? 'agent_in_turn' : 'user_explicit';
|
|
103
|
+
// An explicit request from a person that arms nothing is a refusal, and a script reading the
|
|
104
|
+
// exit code must see it as one (OSK-15413). A check-in the AGENT chose to make is different:
|
|
105
|
+
// there, staying quiet when the budget is spent is the designed outcome.
|
|
106
|
+
if (response.dropped && producer === 'user_explicit') {
|
|
107
|
+
this.failWithCliError({
|
|
108
|
+
message: budgetRefusalMessage(agent.id, response.budget),
|
|
109
|
+
code: 'FOLLOWUP_BUDGET_EXHAUSTED',
|
|
110
|
+
exit: 1,
|
|
111
|
+
retryable: false,
|
|
112
|
+
details: { budget: response.budget ?? null, response },
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
102
116
|
if (flags.json) {
|
|
103
117
|
this.log(JSON.stringify(response, null, 2));
|
|
104
118
|
return;
|
|
105
119
|
}
|
|
106
120
|
const row = response.followup;
|
|
107
121
|
if (response.reused) {
|
|
108
|
-
this.log(
|
|
122
|
+
this.log(reusedMessage(row, subject, flags.why));
|
|
109
123
|
return;
|
|
110
124
|
}
|
|
111
125
|
if (response.dropped) {
|
|
112
|
-
// silent_drop is the check-in budget's contract: the answer to
|
|
113
|
-
// greeting too many" is quiet, not an error the caller can retry.
|
|
126
|
+
// silent_drop is the check-in budget's contract for an agent's own check-in: the answer to
|
|
127
|
+
// "one greeting too many" is quiet, not an error the caller can retry.
|
|
114
128
|
this.log('Dropped by the daily check-in budget — staying quiet is the correct outcome.');
|
|
115
129
|
return;
|
|
116
130
|
}
|
|
@@ -118,3 +132,32 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
exports.default = FollowupsCheckin;
|
|
135
|
+
/**
|
|
136
|
+
* What a reused answer means for the person who asked: a second check-in on a subject that already
|
|
137
|
+
* has one arms nothing and changes nothing, so the note and time they typed were NOT applied. Say
|
|
138
|
+
* so, show what is armed, and name the two ways forward (OSK-15411). Exit stays 0: the follow-up
|
|
139
|
+
* they wanted to exist does exist.
|
|
140
|
+
*/
|
|
141
|
+
function reusedMessage(row, subject, requestedWhy) {
|
|
142
|
+
const lines = [
|
|
143
|
+
`Already armed: ${row?.id} — one pending check-in per subject (${subject.type}:${subject.externalKey}), and this is it.`,
|
|
144
|
+
'Nothing in this request was applied: it armed no new check-in and changed neither the note nor the time.',
|
|
145
|
+
];
|
|
146
|
+
if (row) {
|
|
147
|
+
const sameNote = row.why === requestedWhy;
|
|
148
|
+
lines.push(`Armed: ${row.why ? `"${row.why}"` : '(no note)'}, next ${row.nextRunAt ?? 'when its schedule says'}${sameNote ? ' (the same note you gave)' : ''}.`);
|
|
149
|
+
}
|
|
150
|
+
lines.push(`To change the time: skrr followups reschedule ${row?.id ?? '<id>'} --at <instant> or --in <offset>.`, "To change the note: cancel it and create it again (a cancelled check-in still counts against today's check-in budget).");
|
|
151
|
+
return lines.join('\n');
|
|
152
|
+
}
|
|
153
|
+
/** The refusal for an explicit check-in the daily budget will not take, with the number and the way to raise it. */
|
|
154
|
+
function budgetRefusalMessage(agentId, budget) {
|
|
155
|
+
const { used, limit } = (budget && typeof budget === 'object' ? budget : {});
|
|
156
|
+
const spent = typeof used === 'number' && typeof limit === 'number'
|
|
157
|
+
? `${agentId} has used ${used} of its ${limit} check-ins today`
|
|
158
|
+
: `${agentId} has used its daily check-in budget`;
|
|
159
|
+
return [
|
|
160
|
+
`No check-in was armed: ${spent}. The day resets at 00:00 UTC, and a cancelled check-in still counts.`,
|
|
161
|
+
`Raise the limit with: skrr agents triggers policy ${agentId} --followup-budget checkin=<count>`,
|
|
162
|
+
].join(' ');
|
|
163
|
+
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import { type TaskApprovalResolveParams } from '@skrr-ai/data-provider';
|
|
1
2
|
import { BaseCommand } from '../../../base-command';
|
|
3
|
+
/**
|
|
4
|
+
* The request body for resolving an approval. The server's field is `comment`
|
|
5
|
+
* (`TaskApprovalResolveParams`); `--reason` is only the flag's name. It was sent
|
|
6
|
+
* as `reason` through an `as unknown as` cast, so the type checker could not see
|
|
7
|
+
* the mismatch and the server's non-strict schema dropped the key: the command
|
|
8
|
+
* succeeded and the reason was stored nowhere (OSK-15362).
|
|
9
|
+
*/
|
|
10
|
+
export declare function approvalResolutionBody(flags: {
|
|
11
|
+
status: 'approved' | 'rejected';
|
|
12
|
+
reason?: string;
|
|
13
|
+
}): TaskApprovalResolveParams;
|
|
2
14
|
export default class TasksApprovalsResolve extends BaseCommand {
|
|
3
15
|
static description: string;
|
|
4
16
|
static examples: string[];
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.approvalResolutionBody = approvalResolutionBody;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../../base-command");
|
|
7
|
+
/**
|
|
8
|
+
* The request body for resolving an approval. The server's field is `comment`
|
|
9
|
+
* (`TaskApprovalResolveParams`); `--reason` is only the flag's name. It was sent
|
|
10
|
+
* as `reason` through an `as unknown as` cast, so the type checker could not see
|
|
11
|
+
* the mismatch and the server's non-strict schema dropped the key: the command
|
|
12
|
+
* succeeded and the reason was stored nowhere (OSK-15362).
|
|
13
|
+
*/
|
|
14
|
+
function approvalResolutionBody(flags) {
|
|
15
|
+
const body = { status: flags.status };
|
|
16
|
+
if (flags.reason)
|
|
17
|
+
body.comment = flags.reason;
|
|
18
|
+
return body;
|
|
19
|
+
}
|
|
6
20
|
class TasksApprovalsResolve extends base_command_1.BaseCommand {
|
|
7
21
|
static description = 'Resolve (approve or reject) a task approval';
|
|
8
22
|
static examples = [
|
|
@@ -27,15 +41,15 @@ class TasksApprovalsResolve extends base_command_1.BaseCommand {
|
|
|
27
41
|
this.requireAuth();
|
|
28
42
|
const { args, flags } = await this.parse(TasksApprovalsResolve);
|
|
29
43
|
const taskRef = await this.resolveEntityRef('task', args.id);
|
|
30
|
-
const data = { status: flags.status };
|
|
31
|
-
if (flags.reason)
|
|
32
|
-
data.reason = flags.reason;
|
|
33
44
|
let result;
|
|
34
45
|
try {
|
|
35
46
|
result = await data_provider_1.dataService.resolveTaskApproval({
|
|
36
47
|
taskId: taskRef,
|
|
37
48
|
approvalId: args.approvalId,
|
|
38
|
-
data:
|
|
49
|
+
data: approvalResolutionBody({
|
|
50
|
+
status: flags.status,
|
|
51
|
+
reason: flags.reason,
|
|
52
|
+
}),
|
|
39
53
|
});
|
|
40
54
|
}
|
|
41
55
|
catch (err) {
|
|
@@ -46,7 +60,9 @@ class TasksApprovalsResolve extends base_command_1.BaseCommand {
|
|
|
46
60
|
return;
|
|
47
61
|
}
|
|
48
62
|
const a = result;
|
|
49
|
-
|
|
63
|
+
// Say what was recorded, so a reason that was not stored cannot pass unseen.
|
|
64
|
+
const recorded = typeof a.comment === 'string' && a.comment ? ` (reason: ${a.comment})` : '';
|
|
65
|
+
this.log(`Approval ${a.id ?? a._id ?? args.approvalId} → ${flags.status}${recorded}`);
|
|
50
66
|
}
|
|
51
67
|
}
|
|
52
68
|
exports.default = TasksApprovalsResolve;
|
|
@@ -91,7 +91,9 @@ class TriggersCreate extends base_command_1.BaseCommand {
|
|
|
91
91
|
multiple: true,
|
|
92
92
|
}),
|
|
93
93
|
timezone: core_1.Flags.string({ description: 'IANA timezone for schedule triggers' }),
|
|
94
|
-
'max-fires-per-day': core_1.Flags.integer({
|
|
94
|
+
'max-fires-per-day': core_1.Flags.integer({
|
|
95
|
+
description: 'Daily fire cap (schedule, stochastic and self_scheduled triggers only)',
|
|
96
|
+
}),
|
|
95
97
|
'max-runs': core_1.Flags.integer({ description: 'Lifetime fire cap for schedule triggers' }),
|
|
96
98
|
'end-at': core_1.Flags.string({
|
|
97
99
|
description: 'ISO-8601 datetime after which a schedule stops firing',
|
|
@@ -192,6 +194,9 @@ class TriggersCreate extends base_command_1.BaseCommand {
|
|
|
192
194
|
let payload;
|
|
193
195
|
try {
|
|
194
196
|
payload = await (0, prompt_1.readJsonBody)(flags['from-json']);
|
|
197
|
+
// A flag for a kind other than --kind is refused, not dropped: the block it fills is not
|
|
198
|
+
// sent, so the caller would believe a limit was set that nothing holds (OSK-15491).
|
|
199
|
+
(0, triggers_1.assertKindFlagsApply)(flags.kind ?? payload.kind, flags);
|
|
195
200
|
const refs = (0, triggers_1.parseKeyValueFlags)(flags.ref, '--ref', flags.scope ?? payload.scope?.kind);
|
|
196
201
|
if (flags.scope || Object.keys(refs).length > 0) {
|
|
197
202
|
payload.scope = {
|
|
@@ -267,22 +272,8 @@ class TriggersCreate extends base_command_1.BaseCommand {
|
|
|
267
272
|
filterField: flags['filter-field'],
|
|
268
273
|
filterValues,
|
|
269
274
|
});
|
|
270
|
-
(0, triggers_1.
|
|
271
|
-
|
|
272
|
-
eventType: flags.event,
|
|
273
|
-
filterMode: flags['filter-mode'],
|
|
274
|
-
filterField: flags['filter-field'],
|
|
275
|
-
filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
|
|
276
|
-
debounceMs: flags['debounce-ms'],
|
|
277
|
-
maxPerHour: flags['max-per-hour'],
|
|
278
|
-
});
|
|
279
|
-
const stochastic = {};
|
|
280
|
-
(0, triggers_1.setDefined)(stochastic, {
|
|
281
|
-
probability: flags.probability === undefined ? undefined : Number(flags.probability),
|
|
282
|
-
jitterMs: flags['jitter-ms'],
|
|
283
|
-
maxFiresPerDay: flags['max-fires-per-day'],
|
|
284
|
-
evalIntervalMinutes: flags['eval-interval-minutes'],
|
|
285
|
-
});
|
|
275
|
+
Object.assign(event, (0, triggers_1.buildEventConfigFromFlags)(flags, filterValues));
|
|
276
|
+
const stochastic = (0, triggers_1.buildStochasticConfigFromFlags)(flags);
|
|
286
277
|
const selfScheduled = (0, triggers_1.buildSelfScheduledConfigFromFlags)(flags);
|
|
287
278
|
const form = (0, triggers_1.buildFormConfigFromFlags)(flags);
|
|
288
279
|
const chatMessage = (0, triggers_1.buildChatMessageConfigFromFlags)(flags);
|
|
@@ -41,6 +41,9 @@ class TriggersFire extends base_command_1.BaseCommand {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
this.log(`Fire requested: outcome=${String(result.outcome ?? '-')} reason=${String(result.reason ?? '-')}`);
|
|
44
|
+
const note = (0, triggers_1.describeScheduledFire)(result);
|
|
45
|
+
if (note)
|
|
46
|
+
this.log(note);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
exports.default = TriggersFire;
|