@skrr-ai/cli 0.1.128 → 0.1.130
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.d.ts +12 -0
- package/dist/base-command.js +65 -0
- package/dist/commands/agenda/trigger/create.js +9 -1
- package/dist/commands/agenda/trigger/list.d.ts +2 -1
- package/dist/commands/agenda/trigger/list.js +55 -6
- package/dist/commands/agents/create.js +3 -1
- package/dist/commands/agents/reflect.d.ts +6 -0
- package/dist/commands/agents/reflect.js +45 -6
- package/dist/commands/agents/schedule-overview.d.ts +17 -0
- package/dist/commands/agents/schedule-overview.js +33 -9
- package/dist/commands/agents/triggers/policy.js +3 -1
- package/dist/commands/browser/persistent-profile.d.ts +22 -0
- package/dist/commands/browser/persistent-profile.js +46 -0
- package/dist/commands/commitments/create.js +3 -1
- package/dist/commands/commitments/effective-policy.js +6 -0
- package/dist/commands/commitments/explain.js +10 -1
- package/dist/commands/commitments/plan.d.ts +14 -0
- package/dist/commands/commitments/plan.js +32 -0
- package/dist/commands/commitments/preflight.d.ts +1 -1
- package/dist/commands/commitments/preflight.js +12 -4
- package/dist/commands/commitments/show.d.ts +9 -0
- package/dist/commands/commitments/show.js +14 -1
- package/dist/commands/compass/revisions.d.ts +6 -2
- package/dist/commands/compass/revisions.js +13 -5
- package/dist/commands/compass/tactic.d.ts +5 -0
- package/dist/commands/compass/tactic.js +14 -0
- package/dist/commands/followups/plans/create.js +1 -1
- package/dist/commands/harnesses/list.js +1 -1
- package/dist/commands/northstars/create.js +6 -1
- package/dist/commands/northstars/governance/configure.js +1 -1
- package/dist/commands/northstars/intents/decide.js +2 -3
- package/dist/commands/northstars/list.js +1 -1
- package/dist/commands/northstars/review-now.js +5 -0
- package/dist/commands/northstars/reviews.js +15 -3
- package/dist/commands/northstars/show.js +1 -1
- package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
- package/dist/commands/spaces/auto-executor/runs.js +18 -1
- package/dist/commands/spaces/heartbeats/create.js +7 -5
- package/dist/commands/spaces/heartbeats/status.js +10 -0
- package/dist/commands/spaces/heartbeats/update.js +5 -3
- package/dist/commands/store/artifacts/releases.d.ts +20 -0
- package/dist/commands/store/artifacts/releases.js +85 -0
- package/dist/commands/store/library.d.ts +14 -0
- package/dist/commands/store/library.js +71 -0
- package/dist/commands/tasks/archive.js +6 -0
- package/dist/commands/tasks/list.d.ts +48 -0
- package/dist/commands/tasks/list.js +141 -9
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/remind.js +23 -11
- package/dist/commands/tasks/resume-schedule.js +4 -1
- package/dist/commands/tasks/retry-schedule.js +6 -3
- package/dist/commands/tasks/schedule.js +35 -8
- package/dist/commands/tasks/self-schedule.d.ts +56 -0
- package/dist/commands/tasks/self-schedule.js +179 -21
- package/dist/commands/tasks/show.js +4 -0
- package/dist/lib/agent-trigger-listing.d.ts +26 -0
- package/dist/lib/agent-trigger-listing.js +63 -0
- package/dist/lib/agentic-stream.js +138 -41
- package/dist/lib/commitment-ledgers.js +3 -1
- package/dist/lib/commitments.d.ts +24 -0
- package/dist/lib/commitments.js +64 -4
- package/dist/lib/compasses.d.ts +2 -0
- package/dist/lib/iso-instant.d.ts +52 -0
- package/dist/lib/iso-instant.js +162 -0
- package/dist/lib/northstars.d.ts +87 -4
- package/dist/lib/northstars.js +226 -94
- package/dist/lib/proposals.js +6 -2
- package/dist/lib/reflection.d.ts +58 -0
- package/dist/lib/reflection.js +115 -6
- package/dist/lib/store-artifact-releases.d.ts +39 -0
- package/dist/lib/store-artifact-releases.js +12 -0
- package/dist/lib/store-library.d.ts +37 -0
- package/dist/lib/store-library.js +12 -0
- package/dist/lib/task-calendar.d.ts +19 -0
- package/dist/lib/task-calendar.js +72 -0
- package/dist/lib/task-operations.js +2 -0
- package/dist/lib/task-recurrence-outcome.js +3 -1
- package/dist/lib/task-recurrence-summary.d.ts +15 -0
- package/dist/lib/task-recurrence-summary.js +228 -0
- package/dist/lib/task-reminder.d.ts +28 -0
- package/dist/lib/task-reminder.js +50 -0
- package/dist/lib/task-schedule-notes.d.ts +46 -0
- package/dist/lib/task-schedule-notes.js +71 -0
- package/dist/lib/triggers.d.ts +31 -2
- package/dist/lib/triggers.js +38 -18
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +19 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +28 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6772 -6675
- package/oclif.manifest.json +282 -40
- package/package.json +1 -1
|
@@ -5,6 +5,9 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const web_url_1 = require("../../lib/web-url");
|
|
7
7
|
const prompt_1 = require("../../lib/prompt");
|
|
8
|
+
const task_schedule_notes_1 = require("../../lib/task-schedule-notes");
|
|
9
|
+
const iso_instant_1 = require("../../lib/iso-instant");
|
|
10
|
+
const task_recurrence_summary_1 = require("../../lib/task-recurrence-summary");
|
|
8
11
|
class TasksSchedule extends base_command_1.BaseCommand {
|
|
9
12
|
static description = 'Schedule a task to fire at a future time (optionally recurring)';
|
|
10
13
|
static examples = [
|
|
@@ -21,10 +24,11 @@ class TasksSchedule extends base_command_1.BaseCommand {
|
|
|
21
24
|
description: 'First fire time as ISO-8601 UTC ending in Z (e.g. 2026-06-01T15:30:00Z); numeric offsets such as +00:00 are not accepted',
|
|
22
25
|
}),
|
|
23
26
|
cron: core_1.Flags.string({
|
|
24
|
-
description: 'Recurrence cron pattern (UTC unless --timezone is set)'
|
|
27
|
+
description: 'Recurrence cron pattern: minute hour day-of-month month day-of-week (UTC unless --timezone is set). ' +
|
|
28
|
+
'When both a day of the month and a weekday are set, it fires when EITHER matches',
|
|
25
29
|
}),
|
|
26
30
|
timezone: core_1.Flags.string({
|
|
27
|
-
description: 'IANA
|
|
31
|
+
description: 'IANA zone name for the cron pattern, e.g. Asia/Shanghai or America/Los_Angeles (not an offset such as UTC+8, and not "local")',
|
|
28
32
|
dependsOn: ['cron'],
|
|
29
33
|
}),
|
|
30
34
|
'max-runs': core_1.Flags.integer({
|
|
@@ -32,7 +36,7 @@ class TasksSchedule extends base_command_1.BaseCommand {
|
|
|
32
36
|
dependsOn: ['cron'],
|
|
33
37
|
}),
|
|
34
38
|
'end-date': core_1.Flags.string({
|
|
35
|
-
description: '
|
|
39
|
+
description: 'Last day of the recurrence: YYYY-MM-DD (through the end of that day in --timezone, UTC if omitted) or an ISO-8601 instant with a zone (2026-12-31T17:00:00Z); must be after the first fire',
|
|
36
40
|
dependsOn: ['cron'],
|
|
37
41
|
}),
|
|
38
42
|
'from-json': core_1.Flags.string({
|
|
@@ -52,8 +56,10 @@ class TasksSchedule extends base_command_1.BaseCommand {
|
|
|
52
56
|
}
|
|
53
57
|
const recurrence = flags.cron
|
|
54
58
|
? (0, prompt_1.mergeDefined)({}, {
|
|
55
|
-
|
|
56
|
-
timezone
|
|
59
|
+
// Stored as typed otherwise: a padded pattern came back padded from `show --json`, and a blank
|
|
60
|
+
// `--timezone ""` printed a recurrence with no zone at all (OSK-21531).
|
|
61
|
+
pattern: flags.cron.trim(),
|
|
62
|
+
timezone: flags.timezone?.trim() || undefined,
|
|
57
63
|
maxRuns: flags['max-runs'],
|
|
58
64
|
endDate: flags['end-date'],
|
|
59
65
|
})
|
|
@@ -65,6 +71,11 @@ class TasksSchedule extends base_command_1.BaseCommand {
|
|
|
65
71
|
if (!payload.scheduledFor || typeof payload.scheduledFor !== 'string') {
|
|
66
72
|
this.error('A first-fire time is required. Pass --at <ISO-8601 UTC ending in Z> or include scheduledFor in --from-json using the same format.', { exit: 1 });
|
|
67
73
|
}
|
|
74
|
+
// Said here, in the user's words: the server's zod issue names the REST field (`scheduledFor`) and is the same
|
|
75
|
+
// text for a missing Z, a numeric offset and a day that does not exist (OSK-21507).
|
|
76
|
+
const instant = (0, iso_instant_1.parseUtcInstant)(payload.scheduledFor, flags.at !== undefined ? '--at' : 'scheduledFor');
|
|
77
|
+
if (!instant.ok)
|
|
78
|
+
this.error(instant.message, { exit: 2 });
|
|
68
79
|
let result;
|
|
69
80
|
try {
|
|
70
81
|
result = await data_provider_1.dataService.scheduleTask({
|
|
@@ -81,10 +92,26 @@ class TasksSchedule extends base_command_1.BaseCommand {
|
|
|
81
92
|
}
|
|
82
93
|
const t = result;
|
|
83
94
|
const ref = t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '');
|
|
84
|
-
|
|
95
|
+
const paused = Boolean(t.schedulePaused);
|
|
96
|
+
this.log(`${paused ? 'Recorded the time for' : 'Scheduled'} task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''} for ${payload.scheduledFor}`);
|
|
85
97
|
if (t.recurrence?.pattern) {
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
// Always name the zone: the pattern is read in UTC unless one was set, and a rule with no zone beside it
|
|
99
|
+
// is exactly what could not be told apart from a local-time one.
|
|
100
|
+
const tz = t.recurrence.timezone?.trim() || 'UTC';
|
|
101
|
+
this.log(`Recurrence: ${t.recurrence.pattern} (${tz})`);
|
|
102
|
+
const reading = (0, task_recurrence_summary_1.describeCronPattern)(t.recurrence.pattern);
|
|
103
|
+
if (reading) {
|
|
104
|
+
this.log(` ${reading.text}`);
|
|
105
|
+
if (reading.dayOfMonthOrWeekday) {
|
|
106
|
+
this.log(' With both a day of the month and a weekday, it fires when EITHER matches, not only when both do.');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// The instant the server resolved `--end-date` to, so a calendar date is never a guess.
|
|
110
|
+
if (t.recurrence.endDate)
|
|
111
|
+
this.log(`Ends: ${t.recurrence.endDate}`);
|
|
112
|
+
}
|
|
113
|
+
for (const line of (0, task_schedule_notes_1.scheduleResultLines)(t, String(t.id ?? t._id ?? taskRef), this.config.bin)) {
|
|
114
|
+
this.log(line);
|
|
88
115
|
}
|
|
89
116
|
}
|
|
90
117
|
}
|
|
@@ -23,8 +23,64 @@ export default class TasksSelfSchedule extends BaseCommand {
|
|
|
23
23
|
in: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
24
|
note: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
25
|
'max-per-day': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
're-enable': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
26
27
|
show: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
27
28
|
clear: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
28
29
|
};
|
|
29
30
|
run(): Promise<void>;
|
|
30
31
|
}
|
|
32
|
+
/** What the server records about a return that came due (`lastDecision` on the GET). */
|
|
33
|
+
export interface ReturnDecision {
|
|
34
|
+
outcome?: string;
|
|
35
|
+
/** ran | skipped | failed | waiting. Absent from a server older than this field. */
|
|
36
|
+
verdict?: 'ran' | 'skipped' | 'failed' | 'waiting';
|
|
37
|
+
reason?: string | null;
|
|
38
|
+
/** The reason in plain words, when the server has some for it. */
|
|
39
|
+
reasonText?: string | null;
|
|
40
|
+
at?: string | null;
|
|
41
|
+
}
|
|
42
|
+
/** The part after "the return came due and": what became of it, with the reason. */
|
|
43
|
+
export declare function decisionClause(decision: ReturnDecision): string;
|
|
44
|
+
/**
|
|
45
|
+
* The line `--show` prints for a return whose time has passed. "returned at" is
|
|
46
|
+
* said only when a run really started; a skipped, failed or unrecorded return
|
|
47
|
+
* says what it was.
|
|
48
|
+
*/
|
|
49
|
+
export declare function describeSpentReturn({ taskRef, runAt, decision, triggerId, bin, }: {
|
|
50
|
+
taskRef: string;
|
|
51
|
+
runAt: string;
|
|
52
|
+
decision?: ReturnDecision | null;
|
|
53
|
+
triggerId?: string;
|
|
54
|
+
bin?: string;
|
|
55
|
+
}): string;
|
|
56
|
+
/**
|
|
57
|
+
* `--in` as an instant. A value that does not parse says WHICH value, because
|
|
58
|
+
* the generic "pass --at or --in" this used to fall through to told a person who
|
|
59
|
+
* had passed `--in 1.5h` to pass `--in`.
|
|
60
|
+
*
|
|
61
|
+
* Seconds are accepted: the server owns the 60 s floor and answers
|
|
62
|
+
* `RETURN_TOO_SOON` with the bound, which is a better explanation than the CLI
|
|
63
|
+
* refusing the unit.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveRelativeRunAt(relative: string, now?: number): {
|
|
66
|
+
runAt: string;
|
|
67
|
+
} | {
|
|
68
|
+
error: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* `--at` as a UTC instant. Any offset is accepted and normalised, so the server
|
|
72
|
+
* always receives `...Z`. A value with no zone is refused by saying so: it is a
|
|
73
|
+
* valid ISO-8601 string, and "Invalid ISO datetime" for it read as if the CLI
|
|
74
|
+
* could not parse a thing the help had just told the person to type.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveAbsoluteRunAt(absolute: string): {
|
|
77
|
+
runAt: string;
|
|
78
|
+
} | {
|
|
79
|
+
error: string;
|
|
80
|
+
};
|
|
81
|
+
/** `--at`, or `--in`, or neither (undefined). Absolute wins when both are given. */
|
|
82
|
+
export declare function resolveRunAt(at?: string, relative?: string, now?: number): {
|
|
83
|
+
runAt: string;
|
|
84
|
+
} | {
|
|
85
|
+
error: string;
|
|
86
|
+
} | undefined;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decisionClause = decisionClause;
|
|
4
|
+
exports.describeSpentReturn = describeSpentReturn;
|
|
5
|
+
exports.resolveRelativeRunAt = resolveRelativeRunAt;
|
|
6
|
+
exports.resolveAbsoluteRunAt = resolveAbsoluteRunAt;
|
|
7
|
+
exports.resolveRunAt = resolveRunAt;
|
|
3
8
|
const core_1 = require("@oclif/core");
|
|
4
9
|
const base_command_1 = require("../../base-command");
|
|
5
10
|
const task_extras_1 = require("../../lib/task-extras");
|
|
@@ -20,6 +25,7 @@ class TasksSelfSchedule extends base_command_1.BaseCommand {
|
|
|
20
25
|
static examples = [
|
|
21
26
|
'<%= config.bin %> tasks self-schedule <task-id> --in 2h --note "check whether the deploy settled"',
|
|
22
27
|
'<%= config.bin %> tasks self-schedule <task-id> --at 2026-09-09T14:00:00Z',
|
|
28
|
+
'<%= config.bin %> tasks self-schedule <task-id> --in 90m --re-enable',
|
|
23
29
|
'<%= config.bin %> tasks self-schedule <task-id> --show',
|
|
24
30
|
'<%= config.bin %> tasks self-schedule <task-id> --clear',
|
|
25
31
|
];
|
|
@@ -28,8 +34,14 @@ class TasksSelfSchedule extends base_command_1.BaseCommand {
|
|
|
28
34
|
};
|
|
29
35
|
static flags = {
|
|
30
36
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
31
|
-
at: core_1.Flags.string({
|
|
32
|
-
|
|
37
|
+
at: core_1.Flags.string({
|
|
38
|
+
description: 'Absolute return time: ISO-8601 with a UTC offset, such as 2026-11-05T10:00:00Z or ' +
|
|
39
|
+
'2026-11-05T10:00:00-08:00',
|
|
40
|
+
}),
|
|
41
|
+
in: core_1.Flags.string({
|
|
42
|
+
description: 'Relative return time: a number with a unit (s, m, h, d), such as 90m, 1.5h or 3d. ' +
|
|
43
|
+
'A bare number means minutes. At least 60s, at most 14 days',
|
|
44
|
+
}),
|
|
33
45
|
note: core_1.Flags.string({
|
|
34
46
|
// This DOES reach the run now. It is read by `buildTaskExecutionPrompt`,
|
|
35
47
|
// the one place every dispatch path assembles a task prompt, and rendered
|
|
@@ -40,7 +52,13 @@ class TasksSelfSchedule extends base_command_1.BaseCommand {
|
|
|
40
52
|
'prompt section, and readable any time with --show',
|
|
41
53
|
}),
|
|
42
54
|
'max-per-day': core_1.Flags.integer({
|
|
43
|
-
description: 'Cap on self-initiated returns per day (1-24
|
|
55
|
+
description: 'Cap on self-initiated returns per day (1-24). Omit to keep the cap this task ' +
|
|
56
|
+
'already has (4 for a task that never had one)',
|
|
57
|
+
}),
|
|
58
|
+
're-enable': core_1.Flags.boolean({
|
|
59
|
+
description: "Switch the task's return back on if it was turned off, then park it. Without this a " +
|
|
60
|
+
'switched-off return is left off and the park is refused. A person may; an agent only ' +
|
|
61
|
+
'where its initiative policy allows autonomous loops',
|
|
44
62
|
}),
|
|
45
63
|
show: core_1.Flags.boolean({ description: 'Print the pending return without changing it' }),
|
|
46
64
|
clear: core_1.Flags.boolean({ description: 'Cancel the pending return' }),
|
|
@@ -61,20 +79,47 @@ class TasksSelfSchedule extends base_command_1.BaseCommand {
|
|
|
61
79
|
result = await task_extras_1.taskSelfScheduleApi.clear(taskRef);
|
|
62
80
|
}
|
|
63
81
|
else {
|
|
64
|
-
const
|
|
65
|
-
if (!
|
|
82
|
+
const resolved = resolveRunAt(flags.at, flags.in);
|
|
83
|
+
if (!resolved) {
|
|
66
84
|
this.error('Pass --at <ISO-8601>, --in <duration>, or one of --show / --clear.', {
|
|
67
85
|
exit: 1,
|
|
68
86
|
});
|
|
69
87
|
}
|
|
88
|
+
if ('error' in resolved) {
|
|
89
|
+
this.failWithCliError({
|
|
90
|
+
message: resolved.error,
|
|
91
|
+
code: 'INVALID_RETURN_TIME',
|
|
92
|
+
exit: 1,
|
|
93
|
+
retryable: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// The server's field path, read back as the flag that wrote it.
|
|
97
|
+
this.requestFieldFlags = selfScheduleFieldFlags(Boolean(flags.at));
|
|
70
98
|
result = await task_extras_1.taskSelfScheduleApi.park(taskRef, {
|
|
71
|
-
runAt,
|
|
99
|
+
runAt: resolved.runAt,
|
|
72
100
|
...(flags.note ? { note: flags.note } : {}),
|
|
73
|
-
|
|
101
|
+
// `!== undefined`, not truthiness: `--max-per-day 0` is a value the server
|
|
102
|
+
// refuses (1-24), and dropping it here parked the task with the default
|
|
103
|
+
// while printing a cap the person never asked for.
|
|
104
|
+
...(flags['max-per-day'] !== undefined ? { maxFiresPerDay: flags['max-per-day'] } : {}),
|
|
105
|
+
...(flags['re-enable'] ? { reEnable: true } : {}),
|
|
74
106
|
});
|
|
75
107
|
}
|
|
76
108
|
}
|
|
77
109
|
catch (err) {
|
|
110
|
+
// A return someone switched off is left off. The server's sentence is the
|
|
111
|
+
// same for the web and the API; the CLI adds the flag that answers it.
|
|
112
|
+
if ((0, base_command_1.apiRefusal)(err).code === 'TASK_RETURN_DISABLED') {
|
|
113
|
+
this.failWithCliError({
|
|
114
|
+
message: `Task ${taskRef}'s return is switched off, so it was not parked again.\n` +
|
|
115
|
+
"→ Ask the task's owner to switch it back on in the task's Automations. A person " +
|
|
116
|
+
'who means to turn it back on can run this command again with --re-enable.',
|
|
117
|
+
code: 'TASK_RETURN_DISABLED',
|
|
118
|
+
status: 409,
|
|
119
|
+
exit: 1,
|
|
120
|
+
retryable: false,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
78
123
|
this.handleApiError(err);
|
|
79
124
|
}
|
|
80
125
|
if (flags.json) {
|
|
@@ -82,15 +127,35 @@ class TasksSelfSchedule extends base_command_1.BaseCommand {
|
|
|
82
127
|
return;
|
|
83
128
|
}
|
|
84
129
|
const runAt = result?.runAt;
|
|
130
|
+
const decision = result?.lastDecision;
|
|
85
131
|
if (!runAt) {
|
|
86
132
|
this.log(`Task ${taskRef} has no pending return.`);
|
|
133
|
+
// The interim tick clears the instant after a fire, so the decision may be
|
|
134
|
+
// all there is to say about the return that just went.
|
|
135
|
+
if (decision)
|
|
136
|
+
this.log(`Its last return ${decisionClause(decision)}.`);
|
|
87
137
|
return;
|
|
88
138
|
}
|
|
89
139
|
// `pending` is derived server-side: a return that has already fired keeps
|
|
90
140
|
// its instant on the row, and printing that verbatim would report a return
|
|
91
141
|
// as upcoming an hour after it happened.
|
|
92
142
|
const spent = result?.pending === false;
|
|
93
|
-
|
|
143
|
+
if (!spent) {
|
|
144
|
+
this.log(`Task ${taskRef} returns at ${runAt}`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// "Spent" only means the instant passed. Whether a run started is the
|
|
148
|
+
// decision's to say; "last returned at" was printed for returns that were
|
|
149
|
+
// skipped because the task was blocked, in review, done or archived.
|
|
150
|
+
const triggerId = result?.triggerId;
|
|
151
|
+
this.log(describeSpentReturn({
|
|
152
|
+
taskRef,
|
|
153
|
+
runAt,
|
|
154
|
+
decision,
|
|
155
|
+
triggerId: typeof triggerId === 'string' ? triggerId : undefined,
|
|
156
|
+
bin: this.config.bin,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
94
159
|
if (result?.note)
|
|
95
160
|
this.log(`Note: ${result.note}`);
|
|
96
161
|
if (!spent && result?.maxFiresPerDay) {
|
|
@@ -99,20 +164,113 @@ class TasksSelfSchedule extends base_command_1.BaseCommand {
|
|
|
99
164
|
}
|
|
100
165
|
}
|
|
101
166
|
exports.default = TasksSelfSchedule;
|
|
102
|
-
/**
|
|
103
|
-
function
|
|
167
|
+
/** The part after "the return came due and": what became of it, with the reason. */
|
|
168
|
+
function decisionClause(decision) {
|
|
169
|
+
const why = decision.reasonText || decision.reason || decision.outcome || 'no reason was recorded';
|
|
170
|
+
switch (decision.verdict) {
|
|
171
|
+
case 'ran':
|
|
172
|
+
return 'started a run';
|
|
173
|
+
case 'failed':
|
|
174
|
+
return `could not be started: ${why}`;
|
|
175
|
+
case 'waiting':
|
|
176
|
+
return `has not started yet: ${why}`;
|
|
177
|
+
default:
|
|
178
|
+
return `was skipped: ${why}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The line `--show` prints for a return whose time has passed. "returned at" is
|
|
183
|
+
* said only when a run really started; a skipped, failed or unrecorded return
|
|
184
|
+
* says what it was.
|
|
185
|
+
*/
|
|
186
|
+
function describeSpentReturn({ taskRef, runAt, decision, triggerId, bin = 'skrr', }) {
|
|
187
|
+
if (decision?.verdict === 'ran')
|
|
188
|
+
return `Task ${taskRef} returned at ${runAt}`;
|
|
189
|
+
if (decision)
|
|
190
|
+
return `Task ${taskRef}: the return came due at ${runAt} and ${decisionClause(decision)}.`;
|
|
191
|
+
return (`Task ${taskRef}: the return came due at ${runAt}. Whether it ran is not recorded here; ` +
|
|
192
|
+
`\`${bin} triggers audit ${triggerId ?? `task-self-schedule:${taskRef}`}\` lists every fire.`);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Body paths the server may name in a refusal, and the flag that wrote each. `runAt`
|
|
196
|
+
* is written by whichever of --at / --in the person used, so a "runAt must be at
|
|
197
|
+
* least 60s in the future" for `--in 0m` is not read back as a flag they never typed.
|
|
198
|
+
*/
|
|
199
|
+
function selfScheduleFieldFlags(usedAt) {
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
runAt: usedAt ? '--at' : '--in',
|
|
202
|
+
maxFiresPerDay: '--max-per-day',
|
|
203
|
+
note: '--note',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const MS_PER_UNIT = {
|
|
207
|
+
s: 1_000,
|
|
208
|
+
m: 60_000,
|
|
209
|
+
h: 3_600_000,
|
|
210
|
+
d: 86_400_000,
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* A number, optionally fractional, with an optional unit. The unit words
|
|
214
|
+
* collapse onto their first character (`sec`/`secs`, `min`/`mins`, `hr`/`hrs`,
|
|
215
|
+
* `day`/`days`), which is unambiguous across the four units this accepts.
|
|
216
|
+
*/
|
|
217
|
+
const RELATIVE_PATTERN = /^(\d+(?:\.\d+)?|\.\d+)\s*(s|secs?|seconds?|m|mins?|minutes?|h|hrs?|hours?|d|days?)?$/i;
|
|
218
|
+
/** What a person is told when `--in` is not a duration, in the words they would retype. */
|
|
219
|
+
const RELATIVE_HELP = 'use a number with a unit (s, m, h or d), such as 90m, 1.5h or 3d. A bare number means minutes.';
|
|
220
|
+
/**
|
|
221
|
+
* `--in` as an instant. A value that does not parse says WHICH value, because
|
|
222
|
+
* the generic "pass --at or --in" this used to fall through to told a person who
|
|
223
|
+
* had passed `--in 1.5h` to pass `--in`.
|
|
224
|
+
*
|
|
225
|
+
* Seconds are accepted: the server owns the 60 s floor and answers
|
|
226
|
+
* `RETURN_TOO_SOON` with the bound, which is a better explanation than the CLI
|
|
227
|
+
* refusing the unit.
|
|
228
|
+
*/
|
|
229
|
+
function resolveRelativeRunAt(relative, now = Date.now()) {
|
|
230
|
+
const match = RELATIVE_PATTERN.exec(relative.trim());
|
|
231
|
+
if (!match)
|
|
232
|
+
return { error: `--in ${relative}: ${RELATIVE_HELP}` };
|
|
233
|
+
const unit = (match[2] ?? 'm').toLowerCase()[0];
|
|
234
|
+
const ms = Math.round(Number(match[1]) * MS_PER_UNIT[unit]);
|
|
235
|
+
const at = new Date(now + ms);
|
|
236
|
+
if (!Number.isFinite(at.getTime())) {
|
|
237
|
+
return { error: `--in ${relative}: too far away. A return is at most 14 days out.` };
|
|
238
|
+
}
|
|
239
|
+
return { runAt: at.toISOString() };
|
|
240
|
+
}
|
|
241
|
+
/** An ISO-8601 date and time that names its zone: `Z`, or an offset such as `-08:00`. */
|
|
242
|
+
const ISO_WITH_ZONE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}(?::?\d{2})?)$/i;
|
|
243
|
+
/** The same shape with no zone, which is a real ISO-8601 value and an ambiguous instant. */
|
|
244
|
+
const ISO_WITHOUT_ZONE = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)?$/i;
|
|
245
|
+
const ABSOLUTE_HELP = 'use an ISO-8601 date and time with a UTC offset, such as 2026-11-05T10:00:00Z or ' +
|
|
246
|
+
'2026-11-05T10:00:00-08:00.';
|
|
247
|
+
/**
|
|
248
|
+
* `--at` as a UTC instant. Any offset is accepted and normalised, so the server
|
|
249
|
+
* always receives `...Z`. A value with no zone is refused by saying so: it is a
|
|
250
|
+
* valid ISO-8601 string, and "Invalid ISO datetime" for it read as if the CLI
|
|
251
|
+
* could not parse a thing the help had just told the person to type.
|
|
252
|
+
*/
|
|
253
|
+
function resolveAbsoluteRunAt(absolute) {
|
|
254
|
+
const value = absolute.trim();
|
|
255
|
+
if (ISO_WITH_ZONE.test(value)) {
|
|
256
|
+
const at = new Date(value);
|
|
257
|
+
if (Number.isFinite(at.getTime()))
|
|
258
|
+
return { runAt: at.toISOString() };
|
|
259
|
+
return { error: `--at ${absolute}: that is not a real date and time. ${ABSOLUTE_HELP}` };
|
|
260
|
+
}
|
|
261
|
+
if (ISO_WITHOUT_ZONE.test(value)) {
|
|
262
|
+
return {
|
|
263
|
+
error: `--at ${absolute}: no time zone, so the instant is ambiguous. Add Z for UTC or an ` +
|
|
264
|
+
'offset, such as 2026-11-05T10:00:00Z or 2026-11-05T10:00:00-08:00.',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return { error: `--at ${absolute}: not recognised as a date and time. ${ABSOLUTE_HELP}` };
|
|
268
|
+
}
|
|
269
|
+
/** `--at`, or `--in`, or neither (undefined). Absolute wins when both are given. */
|
|
270
|
+
function resolveRunAt(at, relative, now = Date.now()) {
|
|
104
271
|
if (at)
|
|
105
|
-
return at;
|
|
272
|
+
return resolveAbsoluteRunAt(at);
|
|
106
273
|
if (!relative)
|
|
107
274
|
return undefined;
|
|
108
|
-
|
|
109
|
-
if (!match)
|
|
110
|
-
return undefined;
|
|
111
|
-
const value = Number(match[1]);
|
|
112
|
-
const unit = match[2].toLowerCase();
|
|
113
|
-
const MS_PER_UNIT = { m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
114
|
-
// `min`/`mins`/`hr`/`hrs` collapse onto their first character, which is
|
|
115
|
-
// unambiguous across the three units this accepts.
|
|
116
|
-
const ms = value * (MS_PER_UNIT[unit[0]] ?? MS_PER_UNIT.h);
|
|
117
|
-
return new Date(Date.now() + ms).toISOString();
|
|
275
|
+
return resolveRelativeRunAt(relative, now);
|
|
118
276
|
}
|
|
@@ -23,6 +23,7 @@ const principal_display_1 = require("../../lib/principal-display");
|
|
|
23
23
|
const task_economics_line_1 = require("../../lib/task-economics-line");
|
|
24
24
|
const format_1 = require("../../lib/format");
|
|
25
25
|
const task_recurrence_outcome_1 = require("../../lib/task-recurrence-outcome");
|
|
26
|
+
const task_recurrence_summary_1 = require("../../lib/task-recurrence-summary");
|
|
26
27
|
const record_output_1 = require("../../lib/record-output");
|
|
27
28
|
/**
|
|
28
29
|
* Where to look next — as COMMANDS, not as facts about the task.
|
|
@@ -441,6 +442,9 @@ class TasksShow extends base_command_1.BaseCommand {
|
|
|
441
442
|
this.log(line);
|
|
442
443
|
for (const line of recurrenceArmFailureLines(t, this.config.bin))
|
|
443
444
|
this.log(line);
|
|
445
|
+
// That the Task recurs, by what rule and in which zone (OSK-21531): only `--json` carried it.
|
|
446
|
+
for (const line of (0, task_recurrence_summary_1.recurrenceSummaryLines)(t))
|
|
447
|
+
this.log(line);
|
|
444
448
|
/*
|
|
445
449
|
* Closing is the actor's call — a Task may be `done` with work still
|
|
446
450
|
* outstanding, and `resolution.closure` is where the server recorded what
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `skrr agenda trigger list <agent>` and the triggers it did not show (OSK-21454).
|
|
3
|
+
*
|
|
4
|
+
* `/api/agents/:id/triggers` is the legacy per-agent facade and lists `scope: 'agent'` rows only. A
|
|
5
|
+
* passive agent (`agents create --initial-always-on off`) still owns system-managed Commitment
|
|
6
|
+
* triggers - the daily self-reflection check is `commitment-check:obj_agent_reflection_*`, scope
|
|
7
|
+
* `commitment_watcher` - so the one command whose name says "list this agent's triggers" answered
|
|
8
|
+
* "No triggers." while `triggers list --ref-entity agent:<id>` and the web Upcoming page listed it.
|
|
9
|
+
*
|
|
10
|
+
* The universal list, filtered by the agent ref, is the truth; this reads it and returns the rows the
|
|
11
|
+
* facade left out, marked so a person can tell the platform's from their own.
|
|
12
|
+
*/
|
|
13
|
+
export type TriggerRow = Record<string, unknown>;
|
|
14
|
+
/** Created and run by the platform for a Commitment, not authored by the person. */
|
|
15
|
+
export declare function isSystemManaged(row: TriggerRow): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The universal-list rows the per-agent facade did not return, each tagged `systemManaged`.
|
|
18
|
+
* Rows already listed are not repeated; a row with no id cannot be matched and is kept.
|
|
19
|
+
*/
|
|
20
|
+
export declare function triggersMissingFromAgentList(listed: TriggerRow[], universal: TriggerRow[]): TriggerRow[];
|
|
21
|
+
/** The platform marked this managed trigger suspended (Always-On is off for the agent). */
|
|
22
|
+
export declare function isSuspended(row: TriggerRow): boolean;
|
|
23
|
+
/** What the SOURCE column says: the platform, the scope it belongs to, a turn capture, or nothing. */
|
|
24
|
+
export declare function sourceLabel(row: TriggerRow, unconfirmedTurnCapture: boolean): string;
|
|
25
|
+
/** The legend printed under a table that includes platform-managed rows. */
|
|
26
|
+
export declare function systemManagedLegend(agentId: string, bin?: string): string[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `skrr agenda trigger list <agent>` and the triggers it did not show (OSK-21454).
|
|
4
|
+
*
|
|
5
|
+
* `/api/agents/:id/triggers` is the legacy per-agent facade and lists `scope: 'agent'` rows only. A
|
|
6
|
+
* passive agent (`agents create --initial-always-on off`) still owns system-managed Commitment
|
|
7
|
+
* triggers - the daily self-reflection check is `commitment-check:obj_agent_reflection_*`, scope
|
|
8
|
+
* `commitment_watcher` - so the one command whose name says "list this agent's triggers" answered
|
|
9
|
+
* "No triggers." while `triggers list --ref-entity agent:<id>` and the web Upcoming page listed it.
|
|
10
|
+
*
|
|
11
|
+
* The universal list, filtered by the agent ref, is the truth; this reads it and returns the rows the
|
|
12
|
+
* facade left out, marked so a person can tell the platform's from their own.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.isSystemManaged = isSystemManaged;
|
|
16
|
+
exports.triggersMissingFromAgentList = triggersMissingFromAgentList;
|
|
17
|
+
exports.isSuspended = isSuspended;
|
|
18
|
+
exports.sourceLabel = sourceLabel;
|
|
19
|
+
exports.systemManagedLegend = systemManagedLegend;
|
|
20
|
+
function idOf(row) {
|
|
21
|
+
return typeof row.id === 'string' ? row.id : '';
|
|
22
|
+
}
|
|
23
|
+
function scopeKindOf(row) {
|
|
24
|
+
const scope = row.scope;
|
|
25
|
+
return typeof scope?.kind === 'string' ? scope.kind : '';
|
|
26
|
+
}
|
|
27
|
+
/** Created and run by the platform for a Commitment, not authored by the person. */
|
|
28
|
+
function isSystemManaged(row) {
|
|
29
|
+
return row.producerOwned === true || /^commitment-(check|reflect):/.test(idOf(row));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The universal-list rows the per-agent facade did not return, each tagged `systemManaged`.
|
|
33
|
+
* Rows already listed are not repeated; a row with no id cannot be matched and is kept.
|
|
34
|
+
*/
|
|
35
|
+
function triggersMissingFromAgentList(listed, universal) {
|
|
36
|
+
const known = new Set(listed.map(idOf).filter(Boolean));
|
|
37
|
+
return universal
|
|
38
|
+
.filter((row) => !idOf(row) || !known.has(idOf(row)))
|
|
39
|
+
.map((row) => ({ ...row, systemManaged: isSystemManaged(row) }));
|
|
40
|
+
}
|
|
41
|
+
/** The platform marked this managed trigger suspended (Always-On is off for the agent). */
|
|
42
|
+
function isSuspended(row) {
|
|
43
|
+
const management = row.management;
|
|
44
|
+
return management?.suspended === true;
|
|
45
|
+
}
|
|
46
|
+
/** What the SOURCE column says: the platform, the scope it belongs to, a turn capture, or nothing. */
|
|
47
|
+
function sourceLabel(row, unconfirmedTurnCapture) {
|
|
48
|
+
if (row.systemManaged === true)
|
|
49
|
+
return isSuspended(row) ? 'system, suspended' : 'system';
|
|
50
|
+
if (unconfirmedTurnCapture)
|
|
51
|
+
return 'turn*';
|
|
52
|
+
if (row.systemManaged === false)
|
|
53
|
+
return scopeKindOf(row) || '-';
|
|
54
|
+
return '-';
|
|
55
|
+
}
|
|
56
|
+
/** The legend printed under a table that includes platform-managed rows. */
|
|
57
|
+
function systemManagedLegend(agentId, bin = 'skrr') {
|
|
58
|
+
return [
|
|
59
|
+
'system = created and run by the platform for a Commitment, not by you (for example the daily self-reflection check every agent is seeded with, passive or not).',
|
|
60
|
+
' "suspended" = the platform is holding it because Always-On is off for this agent.',
|
|
61
|
+
` Find the Commitment behind one with: ${bin} commitments list --agent ${agentId} --include-system`,
|
|
62
|
+
];
|
|
63
|
+
}
|