@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
@@ -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({ description: 'Absolute return time (ISO-8601 UTC)' }),
32
- in: core_1.Flags.string({ description: 'Relative return time such as 90m, 2h, 3d' }),
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, default 4)',
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 runAt = resolveRunAt(flags.at, flags.in);
65
- if (!runAt) {
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
- ...(flags['max-per-day'] ? { maxFiresPerDay: flags['max-per-day'] } : {}),
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
- this.log(`Task ${taskRef} ${spent ? 'last returned at' : 'returns at'} ${runAt}`);
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
- /** `90m` / `2h` / `3d`, or an absolute instant. Absolute wins when both are given. */
103
- function resolveRunAt(at, relative) {
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
- const match = /^(\d+)\s*(m|min|mins|h|hr|hrs|d)$/i.exec(relative.trim());
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
  }
@@ -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;
@@ -1,7 +1,8 @@
1
- import { NORTHSTAR_ANALYSIS_MODES, NORTHSTAR_APPROVAL_TRIGGERS, NORTHSTAR_EXECUTION_MODES, NORTHSTAR_INTENT_VERDICTS, NORTHSTAR_MANAGEMENT_INTENT_KINDS, NORTHSTAR_REVIEW_STATUSES } from '@skrr-ai/data-provider';
1
+ import { NORTHSTAR_ANALYSIS_MODES, NORTHSTAR_APPROVAL_TRIGGERS, NORTHSTAR_EXECUTION_MODES, NORTHSTAR_INTENT_VERDICTS, NORTHSTAR_MANAGEMENT_INTENT_KINDS, NORTHSTAR_REASON_WORDS, NORTHSTAR_REVIEW_STATUSES, describeNorthstarReason } from '@skrr-ai/data-provider';
2
2
  import type { Northstar, NorthstarAnalysisMode, NorthstarExecutionMode, NorthstarGovernance, NorthstarGovernanceStatus, NorthstarGovernanceUpdateParams, NorthstarListResponse, NorthstarManagementIntent, NorthstarManagementIntentKind, NorthstarRecommendation, NorthstarReviewRecord, NorthstarStatusResponse } from '@skrr-ai/data-provider';
3
3
  import { withQuery } from './triggers';
4
4
  export { NORTHSTAR_ANALYSIS_MODES, NORTHSTAR_APPROVAL_TRIGGERS, NORTHSTAR_EXECUTION_MODES, NORTHSTAR_INTENT_VERDICTS, NORTHSTAR_MANAGEMENT_INTENT_KINDS, NORTHSTAR_REVIEW_STATUSES, };
5
+ export { NORTHSTAR_REASON_WORDS, describeNorthstarReason };
5
6
  export type { Northstar, NorthstarAnalysisMode, NorthstarExecutionMode, NorthstarGovernance, NorthstarGovernanceStatus, NorthstarManagementIntent, NorthstarManagementIntentKind, NorthstarRecommendation, NorthstarReviewRecord, };
6
7
  /**
7
8
  * oclif's `options:` needs runtime arrays, and these two are LIFECYCLE
@@ -72,9 +73,6 @@ export declare function lookupReview(northstarId: string, typed: string): Promis
72
73
  /** ms → a short human duration, for a table cell that must stay narrow. */
73
74
  export declare function humanMs(ms?: number): string;
74
75
  export declare function shortDate(value?: string): string;
75
- export declare const NORTHSTAR_REASON_WORDS: Readonly<Record<string, string>>;
76
- /** A code in words; an unknown code comes back unchanged, an absent one says so. */
77
- export declare function describeNorthstarReason(code: string | null | undefined): string;
78
76
  export type NorthstarDecisionContext = {
79
77
  northstarId: string;
80
78
  intentId: string;
@@ -131,6 +129,48 @@ export declare function listFooter(input: {
131
129
  nextCursor?: string | null;
132
130
  cursor?: boolean;
133
131
  }): string;
132
+ /** What a northstar with no workspace is called wherever a workspace is named. */
133
+ export declare const PERSONAL_NORTHSTAR_WORKSPACE = "personal";
134
+ /** Why a personal northstar can look missing on the web. The one sentence the others build on. */
135
+ export declare const NORTHSTAR_WEB_SCOPE_SENTENCE: string;
136
+ /** Help text shared by `list` and `show`: where a northstar's workspace is printed. */
137
+ export declare const NORTHSTAR_WORKSPACE_READ_HELP: string;
138
+ /** Help text for `create`: which workspace it lands in, and how to choose one. */
139
+ export declare const NORTHSTAR_WORKSPACE_CREATE_HELP: string;
140
+ /**
141
+ * Anything that may carry a `workspaceId`: a typed Northstar, or the raw record `create` prints (it
142
+ * is read through `Record<string, unknown>` so `--json` is the server's body plus the url).
143
+ */
144
+ type WorkspaceBearing = {
145
+ workspaceId?: unknown;
146
+ } | Record<string, unknown>;
147
+ export declare function shortWorkspaceId(id: string): string;
148
+ /** The workspace a northstar lives in, in one word or id: `personal` when it has none. */
149
+ export declare function northstarWorkspaceLabel(northstar: WorkspaceBearing, options?: {
150
+ short?: boolean;
151
+ }): string;
152
+ /** The line `show` and `create` print: the full workspace id, or `personal`. */
153
+ export declare function northstarWorkspaceLine(northstar: WorkspaceBearing): string;
154
+ /** What the process that ran `create` asked for: the inputs to which note a personal northstar gets. */
155
+ export type PersonalNorthstarContext = {
156
+ /** An Agent session: its workspace comes from the session, and `--workspace` is ignored. */
157
+ autonomous?: boolean;
158
+ /** The workspace the request carried (`--workspace`, or the one `workspaces use` chose). */
159
+ requestedWorkspaceId?: string;
160
+ };
161
+ /**
162
+ * What `create` adds when the northstar landed in no workspace. Three causes, three endings:
163
+ *
164
+ * - an Agent session takes its workspace from the session and ignores `--workspace`, so telling
165
+ * it to pass the flag would send it round in a circle;
166
+ * - a workspace WAS requested but the person is not a member of it: the server does not refuse,
167
+ * it creates the northstar personal (`resolveWorkspaceContext`), so "pass --workspace" would
168
+ * be said to someone who just did;
169
+ * - nothing was requested, and the way out is the flag.
170
+ */
171
+ export declare function personalNorthstarNote(context?: PersonalNorthstarContext): string;
172
+ /** The note for a northstar `create` just made: nothing when it is in a workspace. */
173
+ export declare function createdNorthstarNote(northstar: WorkspaceBearing, context?: PersonalNorthstarContext): string | undefined;
134
174
  export declare function renderNorthstarList(rows: Northstar[], log: (line: string) => void): void;
135
175
  /**
136
176
  * The active-commitment cap, in words. `0` is accepted and stored, and a review