@skrr-ai/cli 0.1.129 → 0.1.131

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.
Files changed (46) hide show
  1. package/dist/base-command.js +30 -0
  2. package/dist/commands/agenda/trigger/create.js +9 -1
  3. package/dist/commands/agent/message/send.js +3 -0
  4. package/dist/commands/agents/reflect.d.ts +6 -0
  5. package/dist/commands/agents/reflect.js +45 -6
  6. package/dist/commands/agents/schedule-overview.d.ts +17 -0
  7. package/dist/commands/agents/schedule-overview.js +33 -9
  8. package/dist/commands/agents/schedule-trace.js +14 -5
  9. package/dist/commands/agents/triggers/policy.js +3 -1
  10. package/dist/commands/harnesses/prune.d.ts +1 -0
  11. package/dist/commands/harnesses/prune.js +24 -4
  12. package/dist/commands/northstars/create.js +6 -1
  13. package/dist/commands/northstars/list.js +1 -1
  14. package/dist/commands/northstars/show.js +1 -1
  15. package/dist/commands/spaces/heartbeats/status.js +10 -0
  16. package/dist/commands/tasks/archive.js +6 -0
  17. package/dist/commands/tasks/pause-schedule.js +1 -1
  18. package/dist/commands/tasks/resume-schedule.js +4 -1
  19. package/dist/commands/tasks/schedule.js +6 -1
  20. package/dist/commands/tasks/self-schedule.d.ts +56 -0
  21. package/dist/commands/tasks/self-schedule.js +179 -21
  22. package/dist/lib/exec-runtime-binary.js +3 -2
  23. package/dist/lib/harnesses.d.ts +4 -1
  24. package/dist/lib/harnesses.js +3 -0
  25. package/dist/lib/northstars.d.ts +44 -4
  26. package/dist/lib/northstars.js +108 -92
  27. package/dist/lib/reflection.d.ts +58 -0
  28. package/dist/lib/reflection.js +115 -6
  29. package/dist/lib/schedule.d.ts +2 -0
  30. package/dist/lib/schedule.js +12 -0
  31. package/dist/lib/task-operations.js +2 -0
  32. package/dist/lib/task-schedule-notes.d.ts +46 -0
  33. package/dist/lib/task-schedule-notes.js +71 -0
  34. package/dist/lib/triggers.d.ts +18 -0
  35. package/dist/lib/triggers.js +16 -6
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -0
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +22 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +2 -0
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +28 -0
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  44. package/dist/node_modules/@skrr-ai/data-provider/index.js +1602 -1578
  45. package/oclif.manifest.json +44 -28
  46. package/package.json +1 -1
@@ -1617,6 +1617,21 @@ class BaseCommand extends core_1.Command {
1617
1617
  details: envelopeDetails(body),
1618
1618
  });
1619
1619
  }
1620
+ else if (commitmentBodyErrorCode(body)) {
1621
+ // These Commitment routes send the code in upper-case `error`, which `refusalCodeOf` does not
1622
+ // read as a code, so the documented code (the guide's refusal table) never reached `error.code`:
1623
+ // a program saw `HTTP_400` or `HTTP_409` and had to know to look in `details.error` (OSK-21102).
1624
+ // Read them by name, as the activation and Compass refusals are, rather than widening
1625
+ // `refusalCodeOf` for every route. The message is the server's, unchanged.
1626
+ this.failWithCliError({
1627
+ message: say(bodyMessage || e.message),
1628
+ code: commitmentBodyErrorCode(body),
1629
+ status: e.status,
1630
+ exit: 1,
1631
+ retryable: false,
1632
+ details: envelopeDetails(body),
1633
+ });
1634
+ }
1620
1635
  else if ((e.status === 409 || e.status === 404) && (0, compass_refusals_1.compassRefusalCode)(body)) {
1621
1636
  // The Compass routes sent the code in upper-case `error`, which `refusalCodeOf` does not
1622
1637
  // read as a code, so a stale writer saw `HTTP_409` and a mistyped version `NOT_FOUND`,
@@ -2309,6 +2324,21 @@ function apiRefusal(err) {
2309
2324
  }
2310
2325
  /** The typed code the server refuses a Commitment activation with. */
2311
2326
  const COMMITMENT_ACTIVATION_BLOCKED = 'COMMITMENT_ACTIVATION_BLOCKED';
2327
+ /**
2328
+ * Commitment refusals the routes send in upper-case `error` (OSK-21102): the retired `mode` and
2329
+ * `policy.autonomyMode` fields, a cadence window no single wall-clock pattern holds, and an edit that
2330
+ * tries to activate a draft (which only `resume` may do).
2331
+ */
2332
+ const COMMITMENT_BODY_ERROR_CODES = new Set([
2333
+ 'COMMITMENT_MODE_RETIRED',
2334
+ 'COMMITMENT_POLICY_RETIRED',
2335
+ 'COMMITMENT_CADENCE_WINDOW_UNSUPPORTED',
2336
+ 'COMMITMENT_RESUME_REQUIRES_EXPLICIT_ACTION',
2337
+ ]);
2338
+ function commitmentBodyErrorCode(body) {
2339
+ const value = stringProp(body, 'error');
2340
+ return value && COMMITMENT_BODY_ERROR_CODES.has(value) ? value : undefined;
2341
+ }
2312
2342
  /**
2313
2343
  * The remedy a refused Commitment activation carries.
2314
2344
  *
@@ -48,7 +48,9 @@ class AgendaTriggerCreate extends base_command_1.BaseCommand {
48
48
  description: 'IANA timezone for the cron (defaults to UTC). Only used with --kind=schedule.',
49
49
  }),
50
50
  'delay-minutes': core_1.Flags.integer({
51
- description: 'Minutes from now to fire — required when --kind=self_scheduled. Range: 5..43200 (5min..30d).',
51
+ description: 'Minutes from now to fire — required when --kind=self_scheduled. ' +
52
+ `Range: ${data_provider_1.AGENDA_SELF_SCHEDULED_DELAY_MIN_MINUTES}..${data_provider_1.AGENDA_SELF_SCHEDULED_DELAY_MAX_MINUTES} ` +
53
+ `(${data_provider_1.AGENDA_SELF_SCHEDULED_DELAY_MIN_MINUTES} minutes..24 hours).`,
52
54
  }),
53
55
  'max-fires-per-day': core_1.Flags.integer({
54
56
  description: 'Daily cap on fires. Required for self_scheduled (1..96); defaults to 96 for schedule when omitted.',
@@ -91,6 +93,12 @@ class AgendaTriggerCreate extends base_command_1.BaseCommand {
91
93
  if (typeof flags['delay-minutes'] !== 'number') {
92
94
  this.error('--delay-minutes is required when --kind=self_scheduled', { exit: 2 });
93
95
  }
96
+ // The server holds the same range, but its refusal arrives after the round trip. The sentence
97
+ // is data-provider's, so what is said here and what the route says cannot differ (OSK-21500).
98
+ const delayRefusal = (0, data_provider_1.describeAgendaDelayMinutesRefusal)(flags['delay-minutes']);
99
+ if (delayRefusal) {
100
+ this.error(`--delay-minutes: ${delayRefusal}`, { exit: 2 });
101
+ }
94
102
  if (typeof flags['max-fires-per-day'] !== 'number') {
95
103
  this.error('--max-fires-per-day is required when --kind=self_scheduled', { exit: 2 });
96
104
  }
@@ -747,6 +747,9 @@ function normalizeIntentPayload({ agentId, jsonBody, merged, }) {
747
747
  const overlayChannel = optionalString(merged.channelId);
748
748
  const overlayThreadTs = optionalString(merged.threadTs);
749
749
  const destinationKind = pickDestinationKind({ bodyDestination, overlayKind });
750
+ if (destinationKind !== 'slack_channel' && (overlayThreadTs || bodyDestination?.threadTs)) {
751
+ throw invalidIntent('--thread-ts / destination.threadTs only applies to slack_channel.');
752
+ }
750
753
  const destination = buildDestination({
751
754
  kind: destinationKind,
752
755
  bodyDestination,
@@ -32,5 +32,11 @@ export default class AgentsReflect extends BaseCommand {
32
32
  run(): Promise<void>;
33
33
  private runDisable;
34
34
  private runEnableOrRetune;
35
+ /**
36
+ * Parse `--set-cadence` and apply the reflection window. A cadence that is not one
37
+ * (`0`, `1 month`, empty) is a usage error naming the rule, raised before anything is
38
+ * written, not an API failure.
39
+ */
40
+ private boundRequestedCadence;
35
41
  private runStatus;
36
42
  }
@@ -55,12 +55,16 @@ class AgentsReflect extends base_command_1.BaseCommand {
55
55
  this.requireAuth();
56
56
  const { args, flags } = await this.parse(AgentsReflect);
57
57
  const agentId = args.id;
58
+ // Only the status view prints whether the loop's fires run, so only it asks the server for
59
+ // that verdict (a policy read); enabling, retuning and pausing do not.
60
+ const isStatus = !flags.disable && !flags.enable && flags['set-cadence'] === undefined;
58
61
  let existing;
59
62
  try {
60
63
  const list = await triggers_1.triggerApi.list((0, triggers_1.withQuery)('/api/triggers', {
61
64
  scope: 'agent',
62
65
  refEntity: (0, triggers_1.agentRefEntity)(agentId),
63
66
  limit: 200,
67
+ include: isStatus ? 'autonomy' : undefined,
64
68
  }));
65
69
  existing = (0, reflection_1.findReflectionTrigger)(list.triggers ?? []);
66
70
  }
@@ -103,14 +107,23 @@ class AgentsReflect extends base_command_1.BaseCommand {
103
107
  // Resolve the target cadence: explicit --set-cadence, else keep the existing
104
108
  // interval when just --enabling, else the default.
105
109
  let minutes;
110
+ let bounds = null;
106
111
  if (flags['set-cadence'] !== undefined) {
107
- minutes = (0, reflection_1.clampCadenceMinutes)((0, reflection_1.parseCadenceToMinutes)(flags['set-cadence']));
112
+ bounds = this.boundRequestedCadence(flags['set-cadence']);
113
+ minutes = bounds.minutes;
108
114
  }
109
115
  else {
110
116
  minutes =
111
117
  (existing && (0, reflection_1.reflectionCadenceMinutes)(existing)) || reflection_1.DEFAULT_REFLECTION_CADENCE_MINUTES;
112
118
  }
113
119
  const cadence = (0, reflection_1.describeCadenceMinutes)(minutes);
120
+ // What was ASKED for, beside what was applied: a request the window moved is said so
121
+ // (OSK-21493), in the text and in `--json`, instead of only the clamped number.
122
+ const clampNote = bounds ? (0, reflection_1.describeCadenceClamp)(bounds) : null;
123
+ const requested = {
124
+ requestedCadence: bounds ? (0, reflection_1.describeCadenceMinutes)(bounds.requestedMinutes) : null,
125
+ ...(clampNote ? { clamped: true, note: clampNote } : {}),
126
+ };
114
127
  if (existing?.id) {
115
128
  const patch = {
116
129
  enabled: true,
@@ -124,10 +137,12 @@ class AgentsReflect extends base_command_1.BaseCommand {
124
137
  }
125
138
  const updated = await triggers_1.triggerApi.update(existing.id, patch);
126
139
  if (flags.json) {
127
- this.log(JSON.stringify({ ok: true, agentId, action: 'retuned', cadence, trigger: updated }, null, 2));
140
+ this.log(JSON.stringify({ ok: true, agentId, action: 'retuned', ...requested, cadence, trigger: updated }, null, 2));
128
141
  return;
129
142
  }
130
143
  this.log(`Self-reflection loop retuned — agent ${agentId} now reflects every ${cadence}.`);
144
+ if (clampNote)
145
+ this.log(`Note: ${clampNote}`);
131
146
  this.log(`Trigger: ${existing.id}`);
132
147
  return;
133
148
  }
@@ -138,12 +153,30 @@ class AgentsReflect extends base_command_1.BaseCommand {
138
153
  });
139
154
  const created = await triggers_1.triggerApi.create(payload);
140
155
  if (flags.json) {
141
- this.log(JSON.stringify({ ok: true, agentId, action: 'enabled', cadence, trigger: created }, null, 2));
156
+ this.log(JSON.stringify({ ok: true, agentId, action: 'enabled', ...requested, cadence, trigger: created }, null, 2));
142
157
  return;
143
158
  }
144
159
  this.log(`Self-reflection loop enabled — agent ${agentId} will reflect every ${cadence}.`);
160
+ if (clampNote)
161
+ this.log(`Note: ${clampNote}`);
145
162
  this.log(`Trigger: ${created.id ?? '(created)'}`);
146
- this.log(`Next run: ${(0, triggers_1.nextRunLabel)(created)}`);
163
+ this.log(`Next run: ${(0, reflection_1.reflectionNextRunLabel)(created)}`);
164
+ }
165
+ /**
166
+ * Parse `--set-cadence` and apply the reflection window. A cadence that is not one
167
+ * (`0`, `1 month`, empty) is a usage error naming the rule, raised before anything is
168
+ * written, not an API failure.
169
+ */
170
+ boundRequestedCadence(raw) {
171
+ try {
172
+ return (0, reflection_1.applyCadenceBounds)((0, reflection_1.parseCadenceToMinutes)(raw));
173
+ }
174
+ catch (err) {
175
+ if (err instanceof reflection_1.InvalidCadenceError) {
176
+ this.failWithCliError({ message: err.message, code: err.code, exit: 2, retryable: false });
177
+ }
178
+ throw err;
179
+ }
147
180
  }
148
181
  runStatus(agentId, existing, json) {
149
182
  if (!existing) {
@@ -165,7 +198,8 @@ class AgentsReflect extends base_command_1.BaseCommand {
165
198
  enabled: existing.enabled !== false,
166
199
  cadence,
167
200
  cadenceMinutes: minutes ?? null,
168
- nextRun: (0, triggers_1.nextRunLabel)(existing),
201
+ nextRun: (0, reflection_1.reflectionNextRunLabel)(existing),
202
+ ...(0, reflection_1.reflectionAutonomyFields)(existing),
169
203
  trigger: existing,
170
204
  }, null, 2));
171
205
  return;
@@ -173,8 +207,13 @@ class AgentsReflect extends base_command_1.BaseCommand {
173
207
  this.log(`Self-reflection loop for agent ${agentId}:`);
174
208
  this.log(` Status: ${existing.enabled === false ? 'paused' : 'active'}`);
175
209
  this.log(` Cadence: every ${cadence}`);
176
- this.log(` Next run: ${(0, triggers_1.nextRunLabel)(existing)}`);
210
+ this.log(` Next run: ${(0, reflection_1.reflectionNextRunLabel)(existing)}`);
177
211
  this.log(` Trigger: ${existing.id ?? '-'}`);
212
+ // "active" with a next run says the loop is armed, not that its fires run: under some
213
+ // initiative modes they only create an inbox suggestion (OSK-21606).
214
+ const autonomyNote = (0, reflection_1.reflectionAutonomyNote)(existing, this.config.bin);
215
+ if (autonomyNote)
216
+ this.log(` Note: ${autonomyNote}`);
178
217
  if (existing.enabled === false) {
179
218
  this.log('');
180
219
  this.log('Re-enable it with: ' + `${this.config.bin} agents reflect ${agentId} --enable`);
@@ -1,4 +1,5 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ import { type TriggerDefinition } from '../../lib/triggers';
2
3
  /**
3
4
  * `skrr agents schedule-overview <agent-id>` — the one-call "full visibility"
4
5
  * view an agent needs when it wakes to think: PAST (recent fires) + FUTURE
@@ -25,3 +26,19 @@ export default class AgentsScheduleOverview extends BaseCommand {
25
26
  };
26
27
  run(): Promise<void>;
27
28
  }
29
+ /** The reflection loop as `--json` reports it. A paused loop has no next run (`-`). */
30
+ export declare function reflectionSummary(reflection: TriggerDefinition): {
31
+ autonomy?: "runs" | "suggests_only";
32
+ note?: string;
33
+ triggerId: string | undefined;
34
+ enabled: boolean;
35
+ cadence: string;
36
+ nextRun: string;
37
+ };
38
+ /** The reflection loop as the text view prints it. */
39
+ export declare function reflectionLine(reflection: TriggerDefinition): string;
40
+ /**
41
+ * The line under the loop when the server says its fires do not run for the owner (OSK-21606),
42
+ * else null. "active · next <time>" only says the loop is armed.
43
+ */
44
+ export declare function reflectionNoteLine(reflection: TriggerDefinition, bin: string): string | null;
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reflectionSummary = reflectionSummary;
4
+ exports.reflectionLine = reflectionLine;
5
+ exports.reflectionNoteLine = reflectionNoteLine;
3
6
  const core_1 = require("@oclif/core");
4
7
  const data_provider_1 = require("@skrr-ai/data-provider");
5
8
  const base_command_1 = require("../../base-command");
@@ -51,6 +54,8 @@ class AgentsScheduleOverview extends base_command_1.BaseCommand {
51
54
  scope: 'agent',
52
55
  refEntity: (0, triggers_1.agentRefEntity)(agentId),
53
56
  limit: 200,
57
+ // Whether the reflection loop's fires run: its row carries the verdict (OSK-21606).
58
+ include: 'autonomy',
54
59
  })),
55
60
  // Filter SERVER-side. Fetching an unfiltered page and filtering in memory
56
61
  // silently truncates the section for any user with more tasks than
@@ -72,14 +77,7 @@ class AgentsScheduleOverview extends base_command_1.BaseCommand {
72
77
  if (flags.json) {
73
78
  this.log(JSON.stringify({
74
79
  agentId,
75
- reflection: reflection
76
- ? {
77
- triggerId: reflection.id,
78
- enabled: reflection.enabled !== false,
79
- cadence: describeCadence(reflection),
80
- nextRun: (0, triggers_1.nextRunLabel)(reflection),
81
- }
82
- : null,
80
+ reflection: reflection ? reflectionSummary(reflection) : null,
83
81
  upcoming,
84
82
  recentFires: fires,
85
83
  openTasks,
@@ -97,7 +95,10 @@ class AgentsScheduleOverview extends base_command_1.BaseCommand {
97
95
  this.log(` none — enable with: ${this.config.bin} agents reflect ${agentId} --set-cadence 6h`);
98
96
  }
99
97
  else {
100
- this.log(` ${reflection.enabled === false ? 'paused' : 'active'} · every ${describeCadence(reflection)} · next ${(0, triggers_1.nextRunLabel)(reflection)} · ${reflection.id ?? '-'}`);
98
+ this.log(reflectionLine(reflection));
99
+ const note = reflectionNoteLine(reflection, this.config.bin);
100
+ if (note)
101
+ this.log(note);
101
102
  }
102
103
  // FUTURE.
103
104
  this.log('');
@@ -190,6 +191,29 @@ function describeCadence(trigger) {
190
191
  const minutes = (0, reflection_1.reflectionCadenceMinutes)(trigger);
191
192
  return minutes ? (0, reflection_1.describeCadenceMinutes)(minutes) : 'unknown';
192
193
  }
194
+ /** The reflection loop as `--json` reports it. A paused loop has no next run (`-`). */
195
+ function reflectionSummary(reflection) {
196
+ return {
197
+ triggerId: reflection.id,
198
+ enabled: reflection.enabled !== false,
199
+ cadence: describeCadence(reflection),
200
+ nextRun: (0, reflection_1.reflectionNextRunLabel)(reflection),
201
+ // Whether its fires run, when the server said; absent means it did not.
202
+ ...(0, reflection_1.reflectionAutonomyFields)(reflection),
203
+ };
204
+ }
205
+ /** The reflection loop as the text view prints it. */
206
+ function reflectionLine(reflection) {
207
+ return ` ${reflection.enabled === false ? 'paused' : 'active'} · every ${describeCadence(reflection)} · next ${(0, reflection_1.reflectionNextRunLabel)(reflection)} · ${reflection.id ?? '-'}`;
208
+ }
209
+ /**
210
+ * The line under the loop when the server says its fires do not run for the owner (OSK-21606),
211
+ * else null. "active · next <time>" only says the loop is armed.
212
+ */
213
+ function reflectionNoteLine(reflection, bin) {
214
+ const note = (0, reflection_1.reflectionAutonomyNote)(reflection, bin);
215
+ return note ? ` Note: ${note}` : null;
216
+ }
193
217
  function settledValue(result) {
194
218
  return result.status === 'fulfilled' ? result.value : undefined;
195
219
  }
@@ -25,7 +25,7 @@ class AgentsScheduleTrace extends base_command_1.BaseCommand {
25
25
  };
26
26
  static flags = {
27
27
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
28
- date: core_1.Flags.string({ description: 'Plan date (YYYY-MM-DD). Defaults to today.' }),
28
+ date: core_1.Flags.string({ description: 'Plan date (YYYY-MM-DD). Defaults to today in the plan timezone.' }),
29
29
  todo: core_1.Flags.string({ description: 'Scope the trace to one to-do' }),
30
30
  timezone: core_1.Flags.string({ description: 'IANA timezone for the plan day' }),
31
31
  limit: core_1.Flags.integer({ description: 'Events to return (1-200)' }),
@@ -34,16 +34,25 @@ class AgentsScheduleTrace extends base_command_1.BaseCommand {
34
34
  async run() {
35
35
  this.requireAuth();
36
36
  const { args, flags } = await this.parse(AgentsScheduleTrace);
37
- // The server requires a plan date. Defaulting to today locally keeps the
38
- // common call a single argument rather than a date the caller must know.
39
- const date = flags.date ?? new Date().toISOString().slice(0, 10);
37
+ // Use the same zone for choosing the day and querying its events. UTC can
38
+ // already be tomorrow while it is still evening for the reader.
39
+ const timezone = flags.timezone ?? (flags.date ? undefined : Intl.DateTimeFormat().resolvedOptions().timeZone);
40
+ let date = flags.date;
41
+ if (!date) {
42
+ try {
43
+ date = (0, schedule_1.scheduleTraceToday)(new Date(), timezone);
44
+ }
45
+ catch {
46
+ this.error(`Invalid IANA timezone: ${timezone}`, { exit: 1 });
47
+ }
48
+ }
40
49
  let trace;
41
50
  try {
42
51
  trace = await schedule_1.scheduleApi.scheduleTrace({
43
52
  agentId: args.agent,
44
53
  date,
45
54
  todoId: flags.todo,
46
- timezone: flags.timezone,
55
+ timezone,
47
56
  limit: flags.limit,
48
57
  cursor: flags.cursor,
49
58
  });
@@ -56,7 +56,9 @@ class AgentsTriggersPolicy extends base_command_1.BaseCommand {
56
56
  description: 'Allowed trigger kind under auto_run_limited (repeatable or comma-separated)',
57
57
  multiple: true,
58
58
  }),
59
- 'max-fires-per-day': core_1.Flags.integer({ description: 'Agent initiative daily fire cap' }),
59
+ 'max-fires-per-day': core_1.Flags.integer({
60
+ description: 'Daily cap on how often this agent can wake itself: its Task returns and self_scheduled triggers, whatever cap they carry',
61
+ }),
60
62
  'max-per-hour': core_1.Flags.integer({ description: 'Agent initiative hourly fire cap' }),
61
63
  'working-hours': core_1.Flags.string({ description: 'Policy workingHours JSON object' }),
62
64
  'quiet-hours': core_1.Flags.string({ description: 'Policy quietHours JSON object' }),
@@ -18,6 +18,7 @@ export default class HarnessesPrune extends BaseCommand {
18
18
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
19
  limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
20
  'older-than': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'include-gone-leases': import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
22
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
23
  };
23
24
  run(): Promise<void>;
@@ -30,12 +30,20 @@ class HarnessesPrune extends base_command_1.BaseCommand {
30
30
  'pinned row is KEPT, with the agents named. Move the pin (`agents attach-harness`) and run this again.\n\n' +
31
31
  'At most --limit rows are removed per run, oldest first; the rest are counted and the next run takes ' +
32
32
  'them. Running it again when nothing qualifies removes nothing. A daemon that is still running ' +
33
- 're-registers on its next connection, so removing a row never stops a machine.';
33
+ 're-registers on its next connection, so removing a row never stops a machine.\n\n' +
34
+ 'A hosted, dedicated or cloud machine belongs to its lease, so by default it is never touched. ' +
35
+ '--include-gone-leases also considers the rows of such a machine whose lease is GONE (archived, ' +
36
+ 'terminating, terminated, failed or lost): no compute runs on it again, so the row can never come ' +
37
+ 'back online. A row with no lease, a lease that can come back (stopped, ready, active) and a row ' +
38
+ 'with usage recorded against it are kept, and so is everything the rules above keep. Use --older-than ' +
39
+ 'to leave recent ones, for example 30d.';
34
40
  static examples = [
35
41
  '<%= config.bin %> harnesses prune',
36
42
  '<%= config.bin %> harnesses prune --yes',
37
43
  '<%= config.bin %> harnesses prune --yes --limit 20',
38
44
  '<%= config.bin %> harnesses prune --older-than 7d --json',
45
+ '<%= config.bin %> harnesses prune --include-gone-leases --older-than 30d',
46
+ '<%= config.bin %> harnesses prune --include-gone-leases --older-than 30d --yes --limit 200',
39
47
  ];
40
48
  static flags = {
41
49
  yes: core_1.Flags.boolean({
@@ -50,6 +58,10 @@ class HarnessesPrune extends base_command_1.BaseCommand {
50
58
  'older-than': core_1.Flags.string({
51
59
  description: 'Only rows unseen for at least this long: 90m, 36h or 7d (default 24h). The server never goes below its offline window.',
52
60
  }),
61
+ 'include-gone-leases': core_1.Flags.boolean({
62
+ description: 'Also consider the rows of hosted, dedicated or cloud machines whose lease is gone. Off by default.',
63
+ default: false,
64
+ }),
53
65
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
54
66
  };
55
67
  async run() {
@@ -71,6 +83,7 @@ class HarnessesPrune extends base_command_1.BaseCommand {
71
83
  dryRun: !flags.yes,
72
84
  ...(flags.limit !== undefined ? { limit: flags.limit } : {}),
73
85
  ...(minUnseenMs !== undefined ? { minUnseenMs } : {}),
86
+ ...(flags['include-gone-leases'] ? { includeGoneLeases: true } : {}),
74
87
  });
75
88
  }
76
89
  catch (err) {
@@ -86,7 +99,9 @@ class HarnessesPrune extends base_command_1.BaseCommand {
86
99
  render(result) {
87
100
  const { dryRun } = result;
88
101
  const removing = dryRun ? result.remove.length : result.removedCount;
89
- const noun = (n) => `${n} superseded harness row${n === 1 ? '' : 's'}`;
102
+ // "superseded" is only true of the personal-machine rows; a gone-lease row was not replaced by anything.
103
+ const kind = result.remove.some((row) => row.goneLease) ? '' : 'superseded ';
104
+ const noun = (n) => `${n} ${kind}harness row${n === 1 ? '' : 's'}`;
90
105
  if (dryRun) {
91
106
  this.log('Dry run - nothing was removed.');
92
107
  }
@@ -141,6 +156,9 @@ class HarnessesPrune extends base_command_1.BaseCommand {
141
156
  if (count('not_personal_machine') > 0) {
142
157
  passedOver.push(`${count('not_personal_machine')} hosted, dedicated, cloud or of unknown origin`);
143
158
  }
159
+ if (count('lease_not_gone') > 0) {
160
+ passedOver.push(`${count('lease_not_gone')} of a machine whose lease is not gone`);
161
+ }
144
162
  if (count('no_machine_identity') > 0) {
145
163
  passedOver.push(`${count('no_machine_identity')} that name no machine`);
146
164
  }
@@ -159,13 +177,15 @@ class HarnessesPrune extends base_command_1.BaseCommand {
159
177
  provider: (0, format_1.formatCell)(row.provider),
160
178
  daemon: (0, format_1.formatCell)(row.daemonId),
161
179
  seen: (0, format_1.localStamp)(row.lastSeenAt),
162
- replacedBy: (0, format_1.formatCell)(row.supersededBy?.daemonId),
180
+ replacedBy: row.goneLease
181
+ ? `lease gone (${row.goneLease})`
182
+ : (0, format_1.formatCell)(row.supersededBy?.daemonId),
163
183
  })), [
164
184
  { key: 'id', header: 'HARNESS ID' },
165
185
  { key: 'provider', header: 'PROVIDER' },
166
186
  { key: 'daemon', header: 'DAEMON ID', verbatim: true },
167
187
  { key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
168
- { key: 'replacedBy', header: 'REPLACED BY (ONLINE)', verbatim: true },
188
+ { key: 'replacedBy', header: 'REPLACED BY (ONLINE) / LEASE', verbatim: true },
169
189
  ], (line) => this.log(line));
170
190
  }
171
191
  }
@@ -15,10 +15,11 @@ const HOUR_MS = 60 * 60 * 1000;
15
15
  * `skrr northstars governance configure`.
16
16
  */
17
17
  class NorthstarsCreate extends base_command_1.BaseCommand {
18
- static description = 'Create a northstar (the portfolio layer above commitments)';
18
+ static description = `Create a northstar (the portfolio layer above commitments)\n\n${northstars_1.NORTHSTAR_WORKSPACE_CREATE_HELP}`;
19
19
  static examples = [
20
20
  '<%= config.bin %> northstars create --title "Reach $10M ARR"',
21
21
  '<%= config.bin %> northstars create --title "Reliability" --tier critical --max-active 5 --review-interval-hours 24',
22
+ '<%= config.bin %> northstars create --title "Reliability" --workspace <workspace-id>',
22
23
  ];
23
24
  static flags = {
24
25
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
@@ -98,6 +99,10 @@ class NorthstarsCreate extends base_command_1.BaseCommand {
98
99
  }
99
100
  this.log(`Created ${created.id} — ${created.title} (${created.status})`);
100
101
  this.log(`URL: ${created.url ?? '-'}`);
102
+ this.log((0, northstars_1.northstarWorkspaceLine)(created));
103
+ const workspaceNote = (0, northstars_1.createdNorthstarNote)(created);
104
+ if (workspaceNote)
105
+ this.log(workspaceNote);
101
106
  if (flags['max-active'] === 0)
102
107
  this.log('Attention max active 0 (no cap)');
103
108
  this.log(flags['review-interval-hours'] !== undefined
@@ -12,7 +12,7 @@ const northstars_1 = require("../../lib/northstars");
12
12
  * their health on a cadence.
13
13
  */
14
14
  class NorthstarsList extends base_command_1.BaseCommand {
15
- static description = 'List northstars (the portfolio layer above commitments)';
15
+ static description = `List northstars (the portfolio layer above commitments)\n\nThe WORKSPACE column is a shortened workspace id (--json has the full workspaceId) or "personal". ${northstars_1.NORTHSTAR_WORKSPACE_READ_HELP}`;
16
16
  static examples = [
17
17
  '<%= config.bin %> northstars list',
18
18
  '<%= config.bin %> northstars list --status active',
@@ -27,7 +27,7 @@ function normalizeNorthstarShowResponse(status) {
27
27
  * asking of a portfolio, which is what is actually in it.
28
28
  */
29
29
  class NorthstarsShow extends base_command_1.BaseCommand {
30
- static description = 'Show a northstar, its linked commitments, and open recommendations';
30
+ static description = `Show a northstar, its linked commitments, and open recommendations\n\nThe Workspace line is the full workspace id, or "personal". ${northstars_1.NORTHSTAR_WORKSPACE_READ_HELP}`;
31
31
  static examples = [
32
32
  '<%= config.bin %> northstars show nst_abc123',
33
33
  '<%= config.bin %> northstars show nst_abc123 --json',
@@ -49,6 +49,16 @@ class SpacesHeartbeatsStatus extends base_command_1.BaseCommand {
49
49
  this.log(`Running: ${s.isRunning === true ? 'yes' : 'no'}`);
50
50
  this.log(`Next Run: ${s.nextRunAt ? (0, format_1.localTime)(s.nextRunAt) : '-'}`);
51
51
  this.log(`BullMQ Job Key: ${s.bullmqJobKey ?? '-'}`);
52
+ // The newest fire and what became of it. `Next Run` moves whether or not a fire does anything,
53
+ // so a heartbeat the agent's policy skips on every fire would otherwise read as healthy.
54
+ const fire = s.lastFire;
55
+ if (fire && typeof fire === 'object') {
56
+ const what = [fire.outcome, fire.reason].filter(Boolean).join(': ');
57
+ this.log(`Last Fire: ${fire.firedAt ? (0, format_1.localTime)(fire.firedAt) : '-'} ${what || '-'}`);
58
+ if (fire.outcome === 'skipped') {
59
+ this.log('The last fire did nothing: the heartbeat was skipped, not run. `skrr triggers audit` lists every fire.');
60
+ }
61
+ }
52
62
  }
53
63
  }
54
64
  exports.default = SpacesHeartbeatsStatus;
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  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
+ const task_schedule_notes_1 = require("../../lib/task-schedule-notes");
7
8
  class TasksArchive extends base_command_1.BaseCommand {
8
9
  static description = 'Archive a task (reversible — use --unarchive to restore)';
9
10
  static examples = [
@@ -37,6 +38,11 @@ class TasksArchive extends base_command_1.BaseCommand {
37
38
  }
38
39
  const action = flags.unarchive ? 'unarchived' : 'archived';
39
40
  this.log(`Task ${args.id} ${action}`);
41
+ const lines = flags.unarchive
42
+ ? (0, task_schedule_notes_1.unarchiveResultLines)(result, String(args.id), this.config.bin)
43
+ : (0, task_schedule_notes_1.archiveResultLines)(result);
44
+ for (const line of lines)
45
+ this.log(line);
40
46
  }
41
47
  }
42
48
  exports.default = TasksArchive;
@@ -5,7 +5,7 @@ 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
  class TasksPauseSchedule extends base_command_1.BaseCommand {
8
- static description = 'Pause future recurring runs; current runs and already queued work are unchanged';
8
+ static description = 'Pause future recurring runs; current runs and already queued work are unchanged. A run that comes due while paused is skipped, not replayed, when you resume';
9
9
  static examples = [
10
10
  '<%= config.bin %> tasks pause-schedule <task-id>',
11
11
  '<%= config.bin %> tasks pause-schedule <task-id> --json',
@@ -4,8 +4,9 @@ const core_1 = require("@oclif/core");
4
4
  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
+ const task_schedule_notes_1 = require("../../lib/task-schedule-notes");
7
8
  class TasksResumeSchedule extends base_command_1.BaseCommand {
8
- static description = 'Resume a paused recurring task — server re-computes the next fire from the cron pattern';
9
+ static description = 'Resume a paused recurring task. A run that came due while it was paused is skipped (not replayed); the next run comes from the cron pattern';
9
10
  static examples = [
10
11
  '<%= config.bin %> tasks resume-schedule <task-id>',
11
12
  '<%= config.bin %> tasks resume-schedule <task-id> --json',
@@ -34,6 +35,8 @@ class TasksResumeSchedule extends base_command_1.BaseCommand {
34
35
  const t = result;
35
36
  const ref = t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '');
36
37
  this.log(`Resumed schedule on task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''}`);
38
+ for (const line of (0, task_schedule_notes_1.resumeResultLines)(t))
39
+ this.log(line);
37
40
  }
38
41
  }
39
42
  exports.default = TasksResumeSchedule;
@@ -5,6 +5,7 @@ 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");
8
9
  const iso_instant_1 = require("../../lib/iso-instant");
9
10
  const task_recurrence_summary_1 = require("../../lib/task-recurrence-summary");
10
11
  class TasksSchedule extends base_command_1.BaseCommand {
@@ -91,7 +92,8 @@ class TasksSchedule extends base_command_1.BaseCommand {
91
92
  }
92
93
  const t = result;
93
94
  const ref = t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '');
94
- this.log(`Scheduled task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''} for ${payload.scheduledFor}`);
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}`);
95
97
  if (t.recurrence?.pattern) {
96
98
  // Always name the zone: the pattern is read in UTC unless one was set, and a rule with no zone beside it
97
99
  // is exactly what could not be told apart from a local-time one.
@@ -108,6 +110,9 @@ class TasksSchedule extends base_command_1.BaseCommand {
108
110
  if (t.recurrence.endDate)
109
111
  this.log(`Ends: ${t.recurrence.endDate}`);
110
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);
115
+ }
111
116
  }
112
117
  }
113
118
  exports.default = TasksSchedule;