@skrr-ai/cli 0.1.130 → 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.
@@ -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,
@@ -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
  });
@@ -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
  }
@@ -75,9 +75,10 @@ function forwardedFlagsNote(verb, opts = {}) {
75
75
  // daemon cannot silently end up on a different deployment than the CLI that
76
76
  // installed it (OSK-279). Saying so is the difference between a flag you may
77
77
  // pass and a flag that is already being passed for you.
78
+ const help = `${base} If \`skrrd\` is not on your PATH, use its installed path instead (typically \`~/.skrr/bin/skrrd\` on macOS and Linux).`;
78
79
  return opts.binds
79
- ? `${base} \`--server\` and \`--profile\` are filled in from your CLI session unless you pass them.`
80
- : base;
80
+ ? `${help} \`--server\` and \`--profile\` are filled in from your CLI session unless you pass them.`
81
+ : help;
81
82
  }
82
83
  /** Extra well-known install locations for one runtime name, checked after PATH. */
83
84
  function fallbackPathsForRuntime(name) {
@@ -243,7 +243,7 @@ export declare function forgetHarness(id: string): Promise<unknown>;
243
243
  * server (`pruneSupersededHarnesses.js`, `REASONS`) and worded once, in
244
244
  * `harnessPruneReasonSentence`, so the table and `--json` never disagree.
245
245
  */
246
- export type HarnessPruneReason = 'recent' | 'latest_registration' | 'no_live_sibling' | 'pinned' | 'granted' | 'default' | 'aliased' | 'changed_since_plan';
246
+ export type HarnessPruneReason = 'recent' | 'latest_registration' | 'no_live_sibling' | 'has_usage' | 'pinned' | 'granted' | 'default' | 'aliased' | 'changed_since_plan';
247
247
  export interface HarnessPruneRow {
248
248
  id: string;
249
249
  daemonId: string | null;
@@ -257,6 +257,8 @@ export interface HarnessPruneRow {
257
257
  daemonId: string | null;
258
258
  lastSeenAt: string | null;
259
259
  };
260
+ /** The gone lease a row this call removes (or would) belonged to (`--include-gone-leases`). */
261
+ goneLease?: string;
260
262
  /** Set on kept rows. */
261
263
  reason?: HarnessPruneReason;
262
264
  /** For `pinned`: who holds the pin. Other people's agents are a count, never named. */
@@ -300,6 +302,7 @@ export declare function pruneHarnesses(options?: {
300
302
  dryRun: boolean;
301
303
  limit?: number;
302
304
  minUnseenMs?: number;
305
+ includeGoneLeases?: boolean;
303
306
  }): Promise<HarnessPruneResult>;
304
307
  /**
305
308
  * `--older-than` takes a whole number with a unit: `90m`, `36h`, `7d`. A bare
@@ -192,6 +192,7 @@ async function pruneHarnesses(options = { dryRun: true }) {
192
192
  dryRun: options.dryRun,
193
193
  ...(options.limit !== undefined ? { limit: options.limit } : {}),
194
194
  ...(options.minUnseenMs !== undefined ? { minUnseenMs: options.minUnseenMs } : {}),
195
+ ...(options.includeGoneLeases ? { includeGoneLeases: true } : {}),
195
196
  },
196
197
  });
197
198
  }
@@ -219,6 +220,8 @@ function harnessPruneReasonSentence(row) {
219
220
  return 'the newest registration of its provider on that machine';
220
221
  case 'no_live_sibling':
221
222
  return 'nothing has replaced it - no daemon on that machine is online for this provider';
223
+ case 'has_usage':
224
+ return 'usage is recorded against it, so the row keeps the name that usage is attributed to';
222
225
  case 'pinned': {
223
226
  const names = (row.detail?.agents ?? []).map((a) => a.name ?? a.id);
224
227
  const more = row.detail?.moreAgents ?? 0;
@@ -145,6 +145,8 @@ export interface ScheduleTrace {
145
145
  has_more?: boolean;
146
146
  next_cursor?: string | null;
147
147
  }
148
+ /** Calendar day at an instant in the zone used for the schedule query. */
149
+ export declare function scheduleTraceToday(now: Date, timezone: string): string;
148
150
  export declare function scheduleTracePath(params: {
149
151
  agentId: string;
150
152
  date: string;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scheduleApi = void 0;
4
+ exports.scheduleTraceToday = scheduleTraceToday;
4
5
  exports.scheduleTracePath = scheduleTracePath;
5
6
  exports.isSmartScheduleProposal = isSmartScheduleProposal;
6
7
  exports.describeNonProposal = describeNonProposal;
@@ -29,6 +30,17 @@ exports.proposalToApproveTodos = proposalToApproveTodos;
29
30
  const data_provider_1 = require("@skrr-ai/data-provider");
30
31
  const format_1 = require("./format");
31
32
  const triggers_1 = require("./triggers");
33
+ /** Calendar day at an instant in the zone used for the schedule query. */
34
+ function scheduleTraceToday(now, timezone) {
35
+ const parts = new Intl.DateTimeFormat('en-CA', {
36
+ timeZone: timezone,
37
+ year: 'numeric',
38
+ month: '2-digit',
39
+ day: '2-digit',
40
+ }).formatToParts(now);
41
+ const part = (type) => parts.find((item) => item.type === type)?.value;
42
+ return `${part('year')}-${part('month')}-${part('day')}`;
43
+ }
32
44
  function scheduleTracePath(params) {
33
45
  return (0, triggers_1.withQuery)('/api/todos/smart-schedule/trace', {
34
46
  agentId: params.agentId,