@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.
- package/dist/base-command.js +30 -0
- package/dist/commands/agenda/trigger/create.js +9 -1
- package/dist/commands/agent/message/send.js +3 -0
- 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/schedule-trace.js +14 -5
- package/dist/commands/agents/triggers/policy.js +3 -1
- package/dist/commands/harnesses/prune.d.ts +1 -0
- package/dist/commands/harnesses/prune.js +24 -4
- package/dist/commands/northstars/create.js +6 -1
- package/dist/commands/northstars/list.js +1 -1
- package/dist/commands/northstars/show.js +1 -1
- package/dist/commands/spaces/heartbeats/status.js +10 -0
- package/dist/commands/tasks/archive.js +6 -0
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/resume-schedule.js +4 -1
- package/dist/commands/tasks/schedule.js +6 -1
- package/dist/commands/tasks/self-schedule.d.ts +56 -0
- package/dist/commands/tasks/self-schedule.js +179 -21
- package/dist/lib/exec-runtime-binary.js +3 -2
- package/dist/lib/harnesses.d.ts +4 -1
- package/dist/lib/harnesses.js +3 -0
- package/dist/lib/northstars.d.ts +44 -4
- package/dist/lib/northstars.js +108 -92
- package/dist/lib/reflection.d.ts +58 -0
- package/dist/lib/reflection.js +115 -6
- package/dist/lib/schedule.d.ts +2 -0
- package/dist/lib/schedule.js +12 -0
- package/dist/lib/task-operations.js +2 -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 +18 -0
- package/dist/lib/triggers.js +16 -6
- 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/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +2 -0
- 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/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +1602 -1578
- package/oclif.manifest.json +44 -28
- package/package.json +1 -1
package/dist/lib/northstars.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.northstarWideFlag = exports.CRITICAL_COMMITMENT_APPROVAL_NOTE = exports.ALLOCATION_INPUT_FLAGS = exports.
|
|
3
|
+
exports.northstarWideFlag = exports.CRITICAL_COMMITMENT_APPROVAL_NOTE = exports.ALLOCATION_INPUT_FLAGS = exports.NORTHSTAR_WORKSPACE_CREATE_HELP = exports.NORTHSTAR_WORKSPACE_READ_HELP = exports.NORTHSTAR_WEB_SCOPE_SENTENCE = exports.PERSONAL_NORTHSTAR_WORKSPACE = exports.NORTHSTAR_LIST_DEFAULT_LIMIT = exports.northstarApi = exports.NORTHSTAR_RECOMMENDATION_STATUSES = exports.NORTHSTAR_PRIORITY_TIERS = exports.NORTHSTAR_STATUSES = exports.describeNorthstarReason = exports.NORTHSTAR_REASON_WORDS = exports.NORTHSTAR_REVIEW_STATUSES = exports.NORTHSTAR_MANAGEMENT_INTENT_KINDS = exports.NORTHSTAR_INTENT_VERDICTS = exports.NORTHSTAR_EXECUTION_MODES = exports.NORTHSTAR_APPROVAL_TRIGGERS = exports.NORTHSTAR_ANALYSIS_MODES = void 0;
|
|
4
4
|
exports.intentRisk = intentRisk;
|
|
5
5
|
exports.shortReviewId = shortReviewId;
|
|
6
6
|
exports.lookupReview = lookupReview;
|
|
7
7
|
exports.humanMs = humanMs;
|
|
8
8
|
exports.shortDate = shortDate;
|
|
9
|
-
exports.describeNorthstarReason = describeNorthstarReason;
|
|
10
9
|
exports.describeDecisionRefusal = describeDecisionRefusal;
|
|
11
10
|
exports.describeDecisionOutcome = describeDecisionOutcome;
|
|
12
11
|
exports.listFooter = listFooter;
|
|
12
|
+
exports.shortWorkspaceId = shortWorkspaceId;
|
|
13
|
+
exports.northstarWorkspaceLabel = northstarWorkspaceLabel;
|
|
14
|
+
exports.northstarWorkspaceLine = northstarWorkspaceLine;
|
|
15
|
+
exports.personalNorthstarNote = personalNorthstarNote;
|
|
16
|
+
exports.createdNorthstarNote = createdNorthstarNote;
|
|
13
17
|
exports.renderNorthstarList = renderNorthstarList;
|
|
14
18
|
exports.describeMaxActive = describeMaxActive;
|
|
15
19
|
exports.renderNorthstar = renderNorthstar;
|
|
@@ -55,7 +59,9 @@ Object.defineProperty(exports, "NORTHSTAR_APPROVAL_TRIGGERS", { enumerable: true
|
|
|
55
59
|
Object.defineProperty(exports, "NORTHSTAR_EXECUTION_MODES", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_EXECUTION_MODES; } });
|
|
56
60
|
Object.defineProperty(exports, "NORTHSTAR_INTENT_VERDICTS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_INTENT_VERDICTS; } });
|
|
57
61
|
Object.defineProperty(exports, "NORTHSTAR_MANAGEMENT_INTENT_KINDS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_MANAGEMENT_INTENT_KINDS; } });
|
|
62
|
+
Object.defineProperty(exports, "NORTHSTAR_REASON_WORDS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_REASON_WORDS; } });
|
|
58
63
|
Object.defineProperty(exports, "NORTHSTAR_REVIEW_STATUSES", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_REVIEW_STATUSES; } });
|
|
64
|
+
Object.defineProperty(exports, "describeNorthstarReason", { enumerable: true, get: function () { return data_provider_1.describeNorthstarReason; } });
|
|
59
65
|
const format_1 = require("./format");
|
|
60
66
|
const triggers_1 = require("./triggers");
|
|
61
67
|
/**
|
|
@@ -169,86 +175,6 @@ function shortDate(value) {
|
|
|
169
175
|
const date = new Date(value);
|
|
170
176
|
return Number.isNaN(date.getTime()) ? '—' : date.toISOString().replace('T', ' ').slice(0, 16);
|
|
171
177
|
}
|
|
172
|
-
/* --------------------------------------------------------------------------
|
|
173
|
-
* Reason codes in words
|
|
174
|
-
*
|
|
175
|
-
* The server refuses, skips and decides with machine codes
|
|
176
|
-
* (`reviewer_agent_not_always_on`, `northstar_version_drift`, ...). A code is
|
|
177
|
-
* for a script; a person reading a table or an error needs the sentence. This
|
|
178
|
-
* is the ONE place those sentences live, so a command never invents its own
|
|
179
|
-
* wording (the old governance line guessed "offline" for a reviewer that was
|
|
180
|
-
* simply not Always-On) and `--json` can carry the same text as `reasonText`.
|
|
181
|
-
*
|
|
182
|
-
* Entries are CLAUSES, lower-case with no trailing full stop, so a caller can
|
|
183
|
-
* put them after "agent turn not started:" or inside a longer sentence. A code
|
|
184
|
-
* with no entry is returned AS IT IS: an unknown code printed raw is honest,
|
|
185
|
-
* a guessed explanation is not.
|
|
186
|
-
* ------------------------------------------------------------------------ */
|
|
187
|
-
exports.NORTHSTAR_REASON_WORDS = {
|
|
188
|
-
// An agent turn that was not reserved for an agent_assisted review.
|
|
189
|
-
no_reviewer_agent_configured: 'no reviewer agent is set on this northstar',
|
|
190
|
-
northstar_paused: 'the northstar is paused',
|
|
191
|
-
northstar_achieved: 'the northstar is marked achieved',
|
|
192
|
-
northstar_archived: 'the northstar is archived',
|
|
193
|
-
analysis_mode_not_agent_assisted: 'analysis mode is deterministic, so no agent is asked',
|
|
194
|
-
managed_agent_missing: 'the reviewer agent does not exist or is not visible to you',
|
|
195
|
-
always_on_control_unavailable: "the reviewer agent's Always-On state could not be read just now",
|
|
196
|
-
always_on_disabled: 'Always-On is turned off for the reviewer agent',
|
|
197
|
-
always_on_suspended: 'Always-On is suspended for the reviewer agent',
|
|
198
|
-
always_on_stale_generation: "the reviewer agent's Always-On state changed while this ran",
|
|
199
|
-
reviewer_agent_foreign_owner: "the reviewer agent belongs to someone other than this northstar's owner",
|
|
200
|
-
reviewer_agent_not_always_on: 'the reviewer agent is not Always-On (it is not offline; turn Always-On on for it)',
|
|
201
|
-
run_already_reserved: 'an agent run was already reserved for this review',
|
|
202
|
-
envelope_build_failed: 'the review could not be packaged for the agent',
|
|
203
|
-
governance_run_reservation_failed: 'reserving the agent turn failed unexpectedly',
|
|
204
|
-
run_not_reserved: 'the run queue declined to reserve the agent turn',
|
|
205
|
-
enqueue_failed: 'the agent turn could not be queued',
|
|
206
|
-
agent_run_reserved: 'an agent turn is reserved; its results land asynchronously',
|
|
207
|
-
// Why an intent was recorded, held or refused by policy.
|
|
208
|
-
execution_mode_advisory: 'execution mode is advisory: recorded for you to read, nothing is changed',
|
|
209
|
-
execution_mode_approval: 'execution mode is approval: it waits for a person',
|
|
210
|
-
proposal_only: 'a proposal: the governor never applies it, a person does',
|
|
211
|
-
commitment_protected: "the commitment is on this northstar's protected list",
|
|
212
|
-
intent_kind_not_allowed: "this kind of intent is not in the northstar's allowed list",
|
|
213
|
-
critical_commitment_lifecycle: 'it changes a critical commitment, which always needs a person',
|
|
214
|
-
resume_requires_approval: 'resuming always needs a person',
|
|
215
|
-
budget_telemetry_unavailable: 'the automatic-change budget could not be read, so it fell back to approval',
|
|
216
|
-
critical_demotion: 'it would demote a critical commitment',
|
|
217
|
-
tier_change_too_large: 'the tier change is larger than one step',
|
|
218
|
-
weight_change_too_large: 'the weight change exceeds the allowed relative change',
|
|
219
|
-
bounds_increase_compute: 'it would buy more compute',
|
|
220
|
-
bounds_change_exceeds_max_relative_change: 'the bounds change exceeds the allowed relative change',
|
|
221
|
-
auto_pause_evidence_incomplete: 'the evidence for an automatic pause is incomplete',
|
|
222
|
-
no_auto_rule_for_kind: 'no automatic rule covers this kind of change',
|
|
223
|
-
auto_mutation_review_cap_reached: 'this review already made its maximum automatic changes',
|
|
224
|
-
auto_mutation_daily_cap_reached: "today's automatic-change cap is used up",
|
|
225
|
-
mutation_cooldown_active: 'the commitment was changed automatically too recently (cooldown)',
|
|
226
|
-
commitment_not_linked_to_northstar: 'the commitment is no longer linked to this northstar',
|
|
227
|
-
commitment_not_found: 'the commitment no longer exists',
|
|
228
|
-
approved_by_user: 'approved by a person',
|
|
229
|
-
rejected_by_user: 'rejected by a person',
|
|
230
|
-
// Why a decision (approve / reject) was refused or did not take effect.
|
|
231
|
-
commitment_version_drift: 'the commitment changed after this was proposed',
|
|
232
|
-
northstar_version_drift: 'the northstar changed after this was proposed',
|
|
233
|
-
// Two ways to the same ending: a newer review asks the same question again, or a concurrent review
|
|
234
|
-
// is already making (or has made) this very change. Either way another review has it.
|
|
235
|
-
superseded: 'another review already proposed or made the same change',
|
|
236
|
-
recommendation_dismissed: 'its recommendation was dismissed',
|
|
237
|
-
intent_not_found: "no such intent in this northstar's 50 most recent reviews",
|
|
238
|
-
review_not_found: 'no such review on this northstar',
|
|
239
|
-
northstar_not_found: 'no such northstar',
|
|
240
|
-
owner_approval_required: "only the northstar's owner can approve a proposal",
|
|
241
|
-
transition_lost: 'another decision landed first',
|
|
242
|
-
claim_lost: 'another process is already applying it',
|
|
243
|
-
mutation_error: 'the change failed part-way and was left for the repair pass',
|
|
244
|
-
proposal_kind_requires_manual_action: 'this kind of proposal is completed by hand',
|
|
245
|
-
};
|
|
246
|
-
/** A code in words; an unknown code comes back unchanged, an absent one says so. */
|
|
247
|
-
function describeNorthstarReason(code) {
|
|
248
|
-
if (!code)
|
|
249
|
-
return 'no reason was recorded';
|
|
250
|
-
return exports.NORTHSTAR_REASON_WORDS[code] ?? code;
|
|
251
|
-
}
|
|
252
178
|
function utcStamp(value) {
|
|
253
179
|
const stamp = shortDate(typeof value === 'string' ? value : undefined);
|
|
254
180
|
return stamp === '—' ? undefined : `${stamp} UTC`;
|
|
@@ -303,7 +229,7 @@ function describeDecisionRefusal(body, ctx) {
|
|
|
303
229
|
const when = utcStamp(body.decidedAt);
|
|
304
230
|
return {
|
|
305
231
|
code,
|
|
306
|
-
text: `This intent was already invalidated${why ? ` (${describeNorthstarReason(why)})` : ''}${when ? ` at ${when}` : ''} — nothing changed.`,
|
|
232
|
+
text: `This intent was already invalidated${why ? ` (${(0, data_provider_1.describeNorthstarReason)(why)})` : ''}${when ? ` at ${when}` : ''} — nothing changed.`,
|
|
307
233
|
};
|
|
308
234
|
}
|
|
309
235
|
let by = '';
|
|
@@ -329,7 +255,7 @@ function describeDecisionRefusal(body, ctx) {
|
|
|
329
255
|
text: `No intent ${ctx.intentId} in this northstar's 50 most recent reviews. If it is older, pass --review <review-id>.`,
|
|
330
256
|
};
|
|
331
257
|
}
|
|
332
|
-
const words = describeNorthstarReason(code);
|
|
258
|
+
const words = (0, data_provider_1.describeNorthstarReason)(code);
|
|
333
259
|
return { code, text: `${words.charAt(0).toUpperCase()}${words.slice(1)}.` };
|
|
334
260
|
}
|
|
335
261
|
/**
|
|
@@ -390,7 +316,7 @@ function describeDecisionOutcome(result, ctx) {
|
|
|
390
316
|
text = `${what}, so the approval was not applied and the intent was invalidated. ${reviewNow}`;
|
|
391
317
|
}
|
|
392
318
|
else if (code) {
|
|
393
|
-
text = `Your approval is recorded, but nothing was applied: ${describeNorthstarReason(code)}.`;
|
|
319
|
+
text = `Your approval is recorded, but nothing was applied: ${(0, data_provider_1.describeNorthstarReason)(code)}.`;
|
|
394
320
|
}
|
|
395
321
|
else {
|
|
396
322
|
text = `Your approval is recorded, but the change has not landed yet (verdict: ${verdict}). It is retried automatically; check \`${bin} northstars intents list ${ctx.northstarId}\`.`;
|
|
@@ -423,6 +349,93 @@ function listFooter(input) {
|
|
|
423
349
|
? `${head} --limit is already at its maximum (${input.max}), so narrow the list with --status`
|
|
424
350
|
: `${head} raise --limit (max ${input.max})`;
|
|
425
351
|
}
|
|
352
|
+
/* --------------------------------------------------------------------------
|
|
353
|
+
* Which workspace a northstar lives in (OSK-21643)
|
|
354
|
+
*
|
|
355
|
+
* The web Northstars list is scoped to the person's ACTIVE workspace and sends `X-Workspace-Id`, so
|
|
356
|
+
* a northstar with no workspace is hidden from it while a workspace is active. The API creates a
|
|
357
|
+
* northstar in the workspace the request names and in none otherwise, which is what a CLI without
|
|
358
|
+
* `--workspace` sends. Nothing on either surface said so, and a northstar made here simply seemed
|
|
359
|
+
* to be missing from the web.
|
|
360
|
+
*
|
|
361
|
+
* Every sentence about it comes from this block, so `list`, `show`, `create`, their `--help` and
|
|
362
|
+
* the docs cannot say different things. The docs quote `personalNorthstarNote` byte for byte and a
|
|
363
|
+
* spec holds them to it.
|
|
364
|
+
* -------------------------------------------------------------------------- */
|
|
365
|
+
/** What a northstar with no workspace is called wherever a workspace is named. */
|
|
366
|
+
exports.PERSONAL_NORTHSTAR_WORKSPACE = 'personal';
|
|
367
|
+
/** Why a personal northstar can look missing on the web. The one sentence the others build on. */
|
|
368
|
+
exports.NORTHSTAR_WEB_SCOPE_SENTENCE = `The ${data_provider_1.BRAND.displayName} web Northstars list shows only the active workspace's Northstars, so a personal one is hidden there while a workspace is active.`;
|
|
369
|
+
/** Help text shared by `list` and `show`: where a northstar's workspace is printed. */
|
|
370
|
+
exports.NORTHSTAR_WORKSPACE_READ_HELP = `A northstar belongs to the workspace it was created in, or is "${exports.PERSONAL_NORTHSTAR_WORKSPACE}" when it has none. ` +
|
|
371
|
+
`${exports.NORTHSTAR_WEB_SCOPE_SENTENCE}`;
|
|
372
|
+
/** Help text for `create`: which workspace it lands in, and how to choose one. */
|
|
373
|
+
exports.NORTHSTAR_WORKSPACE_CREATE_HELP = 'The northstar is created in the workspace the global --workspace <id> flag names, or the one chosen once with ' +
|
|
374
|
+
'`workspaces use`. With neither, or for a workspace you are not a member of, it is personal (no workspace), and ' +
|
|
375
|
+
'the output says so. ' +
|
|
376
|
+
`${exports.NORTHSTAR_WEB_SCOPE_SENTENCE} An Agent session always uses its own session workspace, and --workspace does not change it.`;
|
|
377
|
+
/** The workspace id a northstar carries, or undefined for a personal one (absent, null or blank). */
|
|
378
|
+
function northstarWorkspaceId(northstar) {
|
|
379
|
+
const id = typeof northstar.workspaceId === 'string' ? northstar.workspaceId.trim() : '';
|
|
380
|
+
return id || undefined;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* A workspace id short enough for a table: `038b2251-6a0c-4e2b-9f7d-c1d0f3a8b2aa` is `038b2251…` and
|
|
384
|
+
* `ws_01H8XKPQ7ABCDEF` is `ws_01H8XKPQ…`. Unlike a review id (`shortReviewId`) the short form is NOT
|
|
385
|
+
* accepted back by `--workspace`, which needs the full id, so a shortened value ends in `…` to say
|
|
386
|
+
* it is a prefix. The full id is in `--json` and in `skrr workspaces list`.
|
|
387
|
+
*/
|
|
388
|
+
const WORKSPACE_ID_SHORT_CHARS = 8;
|
|
389
|
+
function shortWorkspaceId(id) {
|
|
390
|
+
const prefixed = /^([a-z]+_)(.+)$/i.exec(id);
|
|
391
|
+
const prefix = prefixed?.[1] ?? '';
|
|
392
|
+
const rest = prefixed?.[2] ?? id;
|
|
393
|
+
return rest.length > WORKSPACE_ID_SHORT_CHARS
|
|
394
|
+
? `${prefix}${rest.slice(0, WORKSPACE_ID_SHORT_CHARS)}…`
|
|
395
|
+
: id;
|
|
396
|
+
}
|
|
397
|
+
/** The workspace a northstar lives in, in one word or id: `personal` when it has none. */
|
|
398
|
+
function northstarWorkspaceLabel(northstar, options = {}) {
|
|
399
|
+
const id = northstarWorkspaceId(northstar);
|
|
400
|
+
if (!id)
|
|
401
|
+
return exports.PERSONAL_NORTHSTAR_WORKSPACE;
|
|
402
|
+
return options.short ? shortWorkspaceId(id) : id;
|
|
403
|
+
}
|
|
404
|
+
/** The line `show` and `create` print: the full workspace id, or `personal`. */
|
|
405
|
+
function northstarWorkspaceLine(northstar) {
|
|
406
|
+
return `Workspace ${northstarWorkspaceLabel(northstar)}`;
|
|
407
|
+
}
|
|
408
|
+
/** Read from the environment `--workspace` and `workspaces use` both write (`withWorkspaceHeader`). */
|
|
409
|
+
function ambientPersonalContext(env = process.env) {
|
|
410
|
+
return {
|
|
411
|
+
autonomous: env.OVERSKY_AUTONOMOUS_SESSION === '1',
|
|
412
|
+
requestedWorkspaceId: env.OVERSKY_WORKSPACE_ID?.trim() || undefined,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* What `create` adds when the northstar landed in no workspace. Three causes, three endings:
|
|
417
|
+
*
|
|
418
|
+
* - an Agent session takes its workspace from the session and ignores `--workspace`, so telling
|
|
419
|
+
* it to pass the flag would send it round in a circle;
|
|
420
|
+
* - a workspace WAS requested but the person is not a member of it: the server does not refuse,
|
|
421
|
+
* it creates the northstar personal (`resolveWorkspaceContext`), so "pass --workspace" would
|
|
422
|
+
* be said to someone who just did;
|
|
423
|
+
* - nothing was requested, and the way out is the flag.
|
|
424
|
+
*/
|
|
425
|
+
function personalNorthstarNote(context = ambientPersonalContext()) {
|
|
426
|
+
const lead = `Not in a workspace (${exports.PERSONAL_NORTHSTAR_WORKSPACE}). ${exports.NORTHSTAR_WEB_SCOPE_SENTENCE}`;
|
|
427
|
+
if (context.autonomous) {
|
|
428
|
+
return `${lead} This Agent session has no workspace to create it in, and --workspace does not change that.`;
|
|
429
|
+
}
|
|
430
|
+
if (context.requestedWorkspaceId) {
|
|
431
|
+
return `${lead} Workspace ${context.requestedWorkspaceId} was requested, but a northstar is created in a workspace only for a member of it: check \`skrr workspaces list\`.`;
|
|
432
|
+
}
|
|
433
|
+
return `${lead} Pass the global --workspace <id> flag to create one inside a workspace.`;
|
|
434
|
+
}
|
|
435
|
+
/** The note for a northstar `create` just made: nothing when it is in a workspace. */
|
|
436
|
+
function createdNorthstarNote(northstar, context) {
|
|
437
|
+
return northstarWorkspaceId(northstar) ? undefined : personalNorthstarNote(context);
|
|
438
|
+
}
|
|
426
439
|
function renderNorthstarList(rows, log) {
|
|
427
440
|
if (rows.length === 0) {
|
|
428
441
|
log('No northstars.');
|
|
@@ -437,6 +450,7 @@ function renderNorthstarList(rows, log) {
|
|
|
437
450
|
commitments: String(row.state?.activeCommitmentCount ?? 0),
|
|
438
451
|
open: String(row.state?.openRecommendationCount ?? 0),
|
|
439
452
|
reviewed: shortDate(row.state?.lastReviewedAt),
|
|
453
|
+
workspace: northstarWorkspaceLabel(row, { short: true }),
|
|
440
454
|
})), [
|
|
441
455
|
{ key: 'id', header: 'ID' },
|
|
442
456
|
{ key: 'title', header: 'TITLE', maxWidth: 40 },
|
|
@@ -446,6 +460,7 @@ function renderNorthstarList(rows, log) {
|
|
|
446
460
|
{ key: 'commitments', header: 'ACTIVE' },
|
|
447
461
|
{ key: 'open', header: 'OPEN REC' },
|
|
448
462
|
{ key: 'reviewed', header: 'REVIEWED' },
|
|
463
|
+
{ key: 'workspace', header: 'WORKSPACE' },
|
|
449
464
|
], log);
|
|
450
465
|
}
|
|
451
466
|
/** A budget that was never set says so; `—` is kept for a timestamp that does not exist yet. */
|
|
@@ -470,6 +485,7 @@ function renderNorthstar(status, log) {
|
|
|
470
485
|
log(`Attention total ${notSet(ns.attentionBudget?.totalWeight)} · max active ${describeMaxActive(ns.attentionBudget?.maxActiveCommitments)}`);
|
|
471
486
|
const cadence = ns.reviewCadence ?? {};
|
|
472
487
|
log(`Review ${cadence.mode ?? 'manual'}${cadence.intervalMs ? ` every ${humanMs(cadence.intervalMs)}` : ''} next ${shortDate(cadence.nextReviewAt)} last ${shortDate(ns.state?.lastReviewedAt)}`);
|
|
488
|
+
log(northstarWorkspaceLine(ns));
|
|
473
489
|
if (ns.goalIds?.length)
|
|
474
490
|
log(`Goals ${ns.goalIds.join(', ')}`);
|
|
475
491
|
if (ns.agentIds?.length)
|
|
@@ -544,15 +560,15 @@ function reviewerReadiness(reviewer) {
|
|
|
544
560
|
if (admission.admitted)
|
|
545
561
|
return ' [ready]';
|
|
546
562
|
if (admission.reason === 'analysis_mode_not_agent_assisted') {
|
|
547
|
-
return ` [not in use: ${describeNorthstarReason(admission.reason)}]`;
|
|
563
|
+
return ` [not in use: ${(0, data_provider_1.describeNorthstarReason)(admission.reason)}]`;
|
|
548
564
|
}
|
|
549
|
-
return ` [not ready: ${describeNorthstarReason(admission.reason)}]`;
|
|
565
|
+
return ` [not ready: ${(0, data_provider_1.describeNorthstarReason)(admission.reason)}]`;
|
|
550
566
|
}
|
|
551
567
|
if (reviewer.ready)
|
|
552
568
|
return ' [ready]';
|
|
553
569
|
// No reason from an older server is "unknown", never a guess: the old fallback
|
|
554
570
|
// said "offline" about an agent that was reachable and merely not Always-On.
|
|
555
|
-
return ` [not ready: ${reviewer.reason ? describeNorthstarReason(reviewer.reason) : 'reason unknown'}]`;
|
|
571
|
+
return ` [not ready: ${reviewer.reason ? (0, data_provider_1.describeNorthstarReason)(reviewer.reason) : 'reason unknown'}]`;
|
|
556
572
|
}
|
|
557
573
|
/** The `governance configure` flags that move what a commitment's allocation is computed from. */
|
|
558
574
|
exports.ALLOCATION_INPUT_FLAGS = [
|
|
@@ -761,13 +777,13 @@ function reviewNote(review) {
|
|
|
761
777
|
}
|
|
762
778
|
function agentTurnNote(review) {
|
|
763
779
|
if (review.status === 'failed') {
|
|
764
|
-
return `failed: ${review.errorMessage ?? describeNorthstarReason(review.reason)}`;
|
|
780
|
+
return `failed: ${review.errorMessage ?? (0, data_provider_1.describeNorthstarReason)(review.reason)}`;
|
|
765
781
|
}
|
|
766
782
|
if (review.status === 'awaiting_agent')
|
|
767
783
|
return 'waiting for the agent turn';
|
|
768
784
|
if (review.analysisMode === 'agent_assisted' && review.status === 'succeeded') {
|
|
769
785
|
if (review.reason && AGENT_TURN_NOT_STARTED.has(review.reason)) {
|
|
770
|
-
return `agent turn not started: ${describeNorthstarReason(review.reason)}`;
|
|
786
|
+
return `agent turn not started: ${(0, data_provider_1.describeNorthstarReason)(review.reason)}`;
|
|
771
787
|
}
|
|
772
788
|
if (!review.autonomousRunId)
|
|
773
789
|
return 'agent turn not started: no reason was recorded';
|
|
@@ -776,7 +792,7 @@ function agentTurnNote(review) {
|
|
|
776
792
|
}
|
|
777
793
|
/** `Reason` line of one review: the words, with the raw code kept beside them for grep. */
|
|
778
794
|
function describeReviewReason(reason) {
|
|
779
|
-
const words = describeNorthstarReason(reason);
|
|
795
|
+
const words = (0, data_provider_1.describeNorthstarReason)(reason);
|
|
780
796
|
return words === reason ? reason : `${words} [${reason}]`;
|
|
781
797
|
}
|
|
782
798
|
function renderReviewList(reviews, log) {
|
|
@@ -848,7 +864,7 @@ function renderIntentList(intents, log, options = {}) {
|
|
|
848
864
|
// The intent's OWN rating (see `intentRisk`), never its recommendation's.
|
|
849
865
|
risk: intentRisk(intent),
|
|
850
866
|
// No sentence from the governor: the policy code in words (an unknown code stays as it is).
|
|
851
|
-
why: intent.reason ?? (intent.verdictReason ? describeNorthstarReason(intent.verdictReason) : '—'),
|
|
867
|
+
why: intent.reason ?? (intent.verdictReason ? (0, data_provider_1.describeNorthstarReason)(intent.verdictReason) : '—'),
|
|
852
868
|
code: intent.verdictReason ?? '',
|
|
853
869
|
source: intent.source ?? 'deterministic',
|
|
854
870
|
}));
|
package/dist/lib/reflection.d.ts
CHANGED
|
@@ -36,15 +36,73 @@ export declare const MAX_REFLECTION_CADENCE_MINUTES: number;
|
|
|
36
36
|
* out to `skrr` from Bash.
|
|
37
37
|
*/
|
|
38
38
|
export declare const REFLECTION_PROMPT = "[Self-reflection] Step back and review your own schedule and cadence \u2014 this is a planning turn, not a chat turn.\n\n1. Look at your whole schedule in one view: your recent fires (past), your upcoming triggers, your open tasks, and today's to-dos. (Use your schedule-overview tool, or run `skrr agents schedule-overview <your-agent-id>`.)\n2. Judge your own rhythm honestly: are you waking too often (many fires, little changed) or too rarely (things slipping past their time)?\n3. If your reflection cadence is wrong, retune it \u2014 reschedule THIS self-reflection trigger to a better interval.\n4. Fix what is slipping: reschedule, create, or cancel triggers and tasks so your plan matches reality.\n\nBe deliberate and minimal. Every trigger you keep spends the user's compute \u2014 keep only what earns its place. If nothing needs changing, do nothing and end the turn.";
|
|
39
|
+
/**
|
|
40
|
+
* A cadence the caller got wrong: unparseable, empty, or not a positive number of
|
|
41
|
+
* minutes. Mirrors `InvalidCadenceError` in the server copy, minus its 4xx `status`:
|
|
42
|
+
* that field is how an MCP server tells the caller's mistake from its own failure,
|
|
43
|
+
* and on the CLI the same field would make `handleApiError` read this as an HTTP
|
|
44
|
+
* response. Commands catch it and report it as a usage error.
|
|
45
|
+
*/
|
|
46
|
+
export declare class InvalidCadenceError extends Error {
|
|
47
|
+
readonly code = "INVALID_CADENCE";
|
|
48
|
+
constructor(message: string);
|
|
49
|
+
}
|
|
39
50
|
export declare function parseCadenceToMinutes(input: string): number;
|
|
40
51
|
/** Clamp a cadence to the allowed reflection window. */
|
|
41
52
|
export declare function clampCadenceMinutes(minutes: number): number;
|
|
53
|
+
/** What `applyCadenceBounds` did to a requested cadence. */
|
|
54
|
+
export interface CadenceBounds {
|
|
55
|
+
minutes: number;
|
|
56
|
+
requestedMinutes: number;
|
|
57
|
+
clamped: boolean;
|
|
58
|
+
/** The limit that moved the request, or null when it was inside the window. */
|
|
59
|
+
bound: 'minimum' | 'maximum' | null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Apply the reflection window to a requested cadence and SAY what happened, so every
|
|
63
|
+
* caller can report "asked 5m, applied 15m" instead of only the clamped number
|
|
64
|
+
* (OSK-21486, OSK-21493, OSK-21602). `bound` names the limit that moved it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function applyCadenceBounds(requestedMinutes: number): CadenceBounds;
|
|
67
|
+
/**
|
|
68
|
+
* One sentence for a clamp ("5m is below the 15m minimum; applied 15m."), or null when
|
|
69
|
+
* the request was inside the window. Shared by the MCP result `note` and the CLI line.
|
|
70
|
+
*/
|
|
71
|
+
export declare function describeCadenceClamp({ minutes, requestedMinutes, bound, }: Pick<CadenceBounds, 'minutes' | 'requestedMinutes' | 'bound'>): string | null;
|
|
42
72
|
/** Render minutes back as a compact human cadence ("6h", "90m", "1d"). */
|
|
43
73
|
export declare function describeCadenceMinutes(minutes: number): string;
|
|
44
74
|
/** The agent's single self-reflection trigger, or undefined if none exists. */
|
|
45
75
|
export declare function findReflectionTrigger(triggers: TriggerDefinition[]): TriggerDefinition | undefined;
|
|
46
76
|
/** Read a reflection trigger's current interval in minutes, if it has one. */
|
|
47
77
|
export declare function reflectionCadenceMinutes(trigger: TriggerDefinition): number | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* The loop's next run, for display: its scheduled time while it is enabled, else `-`. A
|
|
80
|
+
* paused loop has no next run, and a loop with nothing scheduled does not have its LAST fire
|
|
81
|
+
* promoted to one (the server copy keeps `nextRunAt` and `lastFiredAt` apart the same way).
|
|
82
|
+
*/
|
|
83
|
+
export declare function reflectionNextRunLabel(trigger: TriggerDefinition): string;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the loop's fires run, as the SERVER says it (`GET /api/triggers?include=autonomy`), or
|
|
86
|
+
* null when it did not say. Nothing is derived here: the verdict depends on the agent's
|
|
87
|
+
* initiative mode and on which door created the loop, and a local copy of that judgement is free
|
|
88
|
+
* to disagree with the fire path (OSK-21606). A server that predates the field, or one that could
|
|
89
|
+
* not reach a verdict, sends nothing, and that is UNKNOWN, never `runs`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function reflectionAutonomy(trigger: TriggerDefinition): {
|
|
92
|
+
autonomy: 'runs' | 'suggests_only';
|
|
93
|
+
note?: string;
|
|
94
|
+
} | null;
|
|
95
|
+
/**
|
|
96
|
+
* The one plain sentence a text view prints under the loop when its fires will not run for the
|
|
97
|
+
* owner; null when they do, when the server did not say, or when the loop is paused (a paused
|
|
98
|
+
* loop fires nothing, so the gate is not what the reader needs to hear about).
|
|
99
|
+
*/
|
|
100
|
+
export declare function reflectionAutonomyNote(trigger: TriggerDefinition, bin: string): string | null;
|
|
101
|
+
/** The `--json` fields for the same verdict: absent when unknown, so absence reads as unknown. */
|
|
102
|
+
export declare function reflectionAutonomyFields(trigger: TriggerDefinition): {
|
|
103
|
+
autonomy?: 'runs' | 'suggests_only';
|
|
104
|
+
note?: string;
|
|
105
|
+
};
|
|
48
106
|
/**
|
|
49
107
|
* Build the `POST /api/triggers` body for a fresh self-reflection loop. Uses the
|
|
50
108
|
* proven `scope:agent + kind:schedule + mode:run_only` shape and coalesces so a
|
package/dist/lib/reflection.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REFLECTION_PROMPT = exports.MAX_REFLECTION_CADENCE_MINUTES = exports.MIN_REFLECTION_CADENCE_MINUTES = exports.DEFAULT_REFLECTION_CADENCE_MINUTES = exports.REFLECTION_LABEL = void 0;
|
|
3
|
+
exports.InvalidCadenceError = exports.REFLECTION_PROMPT = exports.MAX_REFLECTION_CADENCE_MINUTES = exports.MIN_REFLECTION_CADENCE_MINUTES = exports.DEFAULT_REFLECTION_CADENCE_MINUTES = exports.REFLECTION_LABEL = void 0;
|
|
4
4
|
exports.parseCadenceToMinutes = parseCadenceToMinutes;
|
|
5
5
|
exports.clampCadenceMinutes = clampCadenceMinutes;
|
|
6
|
+
exports.applyCadenceBounds = applyCadenceBounds;
|
|
7
|
+
exports.describeCadenceClamp = describeCadenceClamp;
|
|
6
8
|
exports.describeCadenceMinutes = describeCadenceMinutes;
|
|
7
9
|
exports.findReflectionTrigger = findReflectionTrigger;
|
|
8
10
|
exports.reflectionCadenceMinutes = reflectionCadenceMinutes;
|
|
11
|
+
exports.reflectionNextRunLabel = reflectionNextRunLabel;
|
|
12
|
+
exports.reflectionAutonomy = reflectionAutonomy;
|
|
13
|
+
exports.reflectionAutonomyNote = reflectionAutonomyNote;
|
|
14
|
+
exports.reflectionAutonomyFields = reflectionAutonomyFields;
|
|
9
15
|
exports.buildReflectionTriggerPayload = buildReflectionTriggerPayload;
|
|
10
16
|
/**
|
|
11
17
|
* reflection.ts — the agent self-reflection ("meta cadence") loop.
|
|
@@ -52,9 +58,42 @@ exports.REFLECTION_PROMPT = `[Self-reflection] Step back and review your own sch
|
|
|
52
58
|
4. Fix what is slipping: reschedule, create, or cancel triggers and tasks so your plan matches reality.
|
|
53
59
|
|
|
54
60
|
Be deliberate and minimal. Every trigger you keep spends the user's compute — keep only what earns its place. If nothing needs changing, do nothing and end the turn.`;
|
|
61
|
+
/**
|
|
62
|
+
* A cadence the caller got wrong: unparseable, empty, or not a positive number of
|
|
63
|
+
* minutes. Mirrors `InvalidCadenceError` in the server copy, minus its 4xx `status`:
|
|
64
|
+
* that field is how an MCP server tells the caller's mistake from its own failure,
|
|
65
|
+
* and on the CLI the same field would make `handleApiError` read this as an HTTP
|
|
66
|
+
* response. Commands catch it and report it as a usage error.
|
|
67
|
+
*/
|
|
68
|
+
class InvalidCadenceError extends Error {
|
|
69
|
+
code = 'INVALID_CADENCE';
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = 'InvalidCadenceError';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.InvalidCadenceError = InvalidCadenceError;
|
|
76
|
+
/**
|
|
77
|
+
* A cadence must be a positive, finite number of minutes. `0` is not "off" and not "the
|
|
78
|
+
* minimum": rounding it up to the floor turned an attempt to switch the loop off into one
|
|
79
|
+
* that wakes the agent 96 times a day (OSK-21493).
|
|
80
|
+
*/
|
|
81
|
+
function requirePositiveMinutes(minutes, raw) {
|
|
82
|
+
if (!Number.isFinite(minutes)) {
|
|
83
|
+
throw new InvalidCadenceError(`That cadence is too large to be a number of minutes. Pick between ${describeCadenceMinutes(exports.MIN_REFLECTION_CADENCE_MINUTES)} and ${describeCadenceMinutes(exports.MAX_REFLECTION_CADENCE_MINUTES)}.`);
|
|
84
|
+
}
|
|
85
|
+
if (minutes <= 0) {
|
|
86
|
+
const shown = raw.length > 24 ? `${raw.slice(0, 24)}…` : raw;
|
|
87
|
+
throw new InvalidCadenceError(`"${shown}" is not a cadence — it cannot mean "off". To pause the loop, disable it instead ` +
|
|
88
|
+
'(skrr agents reflect <agent> --disable, or the self_reflection_disable tool); otherwise ' +
|
|
89
|
+
`pick between ${describeCadenceMinutes(exports.MIN_REFLECTION_CADENCE_MINUTES)} and ${describeCadenceMinutes(exports.MAX_REFLECTION_CADENCE_MINUTES)}.`);
|
|
90
|
+
}
|
|
91
|
+
return minutes;
|
|
92
|
+
}
|
|
55
93
|
/**
|
|
56
94
|
* Parse a human cadence into minutes. Accepts `6h`, `90m`, `2h30m`, `1d`, or a
|
|
57
|
-
* bare number (interpreted as minutes). Throws on
|
|
95
|
+
* bare number (interpreted as minutes). Throws `InvalidCadenceError` on
|
|
96
|
+
* unparseable input.
|
|
58
97
|
*/
|
|
59
98
|
// Longest-first alternation, and CADENCE_FULL is anchored so the WHOLE string
|
|
60
99
|
// must be consumed. Without the anchor, "1 month" matches `1` + `m` and silently
|
|
@@ -67,14 +106,14 @@ function parseCadenceToMinutes(input) {
|
|
|
67
106
|
.trim()
|
|
68
107
|
.toLowerCase();
|
|
69
108
|
if (!raw) {
|
|
70
|
-
throw new
|
|
109
|
+
throw new InvalidCadenceError('Empty cadence. Use a form like "6h", "90m", "2h30m", or "360".');
|
|
71
110
|
}
|
|
72
111
|
// Bare number → minutes.
|
|
73
112
|
if (/^\d+(\.\d+)?$/.test(raw)) {
|
|
74
|
-
return Math.round(Number(raw));
|
|
113
|
+
return requirePositiveMinutes(Math.round(Number(raw)), raw);
|
|
75
114
|
}
|
|
76
115
|
if (!CADENCE_FULL.test(raw)) {
|
|
77
|
-
throw new
|
|
116
|
+
throw new InvalidCadenceError(`Could not parse cadence "${input}". Use minutes/hours/days — e.g. "6h", "90m", "2h30m", "360". ` +
|
|
78
117
|
'(Months and seconds are not units here.)');
|
|
79
118
|
}
|
|
80
119
|
let total = 0;
|
|
@@ -90,12 +129,39 @@ function parseCadenceToMinutes(input) {
|
|
|
90
129
|
else
|
|
91
130
|
total += n;
|
|
92
131
|
}
|
|
93
|
-
return Math.round(total);
|
|
132
|
+
return requirePositiveMinutes(Math.round(total), raw);
|
|
94
133
|
}
|
|
95
134
|
/** Clamp a cadence to the allowed reflection window. */
|
|
96
135
|
function clampCadenceMinutes(minutes) {
|
|
97
136
|
return Math.max(exports.MIN_REFLECTION_CADENCE_MINUTES, Math.min(exports.MAX_REFLECTION_CADENCE_MINUTES, Math.round(minutes)));
|
|
98
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Apply the reflection window to a requested cadence and SAY what happened, so every
|
|
140
|
+
* caller can report "asked 5m, applied 15m" instead of only the clamped number
|
|
141
|
+
* (OSK-21486, OSK-21493, OSK-21602). `bound` names the limit that moved it.
|
|
142
|
+
*/
|
|
143
|
+
function applyCadenceBounds(requestedMinutes) {
|
|
144
|
+
const requested = Math.round(requestedMinutes);
|
|
145
|
+
const minutes = clampCadenceMinutes(requested);
|
|
146
|
+
let bound = null;
|
|
147
|
+
if (minutes > requested)
|
|
148
|
+
bound = 'minimum';
|
|
149
|
+
else if (minutes < requested)
|
|
150
|
+
bound = 'maximum';
|
|
151
|
+
return { minutes, requestedMinutes: requested, clamped: bound !== null, bound };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* One sentence for a clamp ("5m is below the 15m minimum; applied 15m."), or null when
|
|
155
|
+
* the request was inside the window. Shared by the MCP result `note` and the CLI line.
|
|
156
|
+
*/
|
|
157
|
+
function describeCadenceClamp({ minutes, requestedMinutes, bound, }) {
|
|
158
|
+
if (!bound)
|
|
159
|
+
return null;
|
|
160
|
+
const limit = bound === 'minimum' ? exports.MIN_REFLECTION_CADENCE_MINUTES : exports.MAX_REFLECTION_CADENCE_MINUTES;
|
|
161
|
+
const side = bound === 'minimum' ? 'below' : 'above';
|
|
162
|
+
return (`${describeCadenceMinutes(requestedMinutes)} is ${side} the ${describeCadenceMinutes(limit)} ` +
|
|
163
|
+
`${bound}; applied ${describeCadenceMinutes(minutes)}.`);
|
|
164
|
+
}
|
|
99
165
|
/** Render minutes back as a compact human cadence ("6h", "90m", "1d"). */
|
|
100
166
|
function describeCadenceMinutes(minutes) {
|
|
101
167
|
if (minutes > 0 && minutes % (60 * 24) === 0)
|
|
@@ -113,6 +179,49 @@ function reflectionCadenceMinutes(trigger) {
|
|
|
113
179
|
const ms = trigger.schedule?.intervalMs;
|
|
114
180
|
return typeof ms === 'number' && Number.isFinite(ms) ? Math.round(ms / 60_000) : undefined;
|
|
115
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The loop's next run, for display: its scheduled time while it is enabled, else `-`. A
|
|
184
|
+
* paused loop has no next run, and a loop with nothing scheduled does not have its LAST fire
|
|
185
|
+
* promoted to one (the server copy keeps `nextRunAt` and `lastFiredAt` apart the same way).
|
|
186
|
+
*/
|
|
187
|
+
function reflectionNextRunLabel(trigger) {
|
|
188
|
+
return (0, triggers_1.scheduledRunAt)(trigger) ?? '-';
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Whether the loop's fires run, as the SERVER says it (`GET /api/triggers?include=autonomy`), or
|
|
192
|
+
* null when it did not say. Nothing is derived here: the verdict depends on the agent's
|
|
193
|
+
* initiative mode and on which door created the loop, and a local copy of that judgement is free
|
|
194
|
+
* to disagree with the fire path (OSK-21606). A server that predates the field, or one that could
|
|
195
|
+
* not reach a verdict, sends nothing, and that is UNKNOWN, never `runs`.
|
|
196
|
+
*/
|
|
197
|
+
function reflectionAutonomy(trigger) {
|
|
198
|
+
if (trigger.enabled === false)
|
|
199
|
+
return null;
|
|
200
|
+
const { autonomy, autonomyNote } = trigger;
|
|
201
|
+
if (autonomy !== 'runs' && autonomy !== 'suggests_only')
|
|
202
|
+
return null;
|
|
203
|
+
const note = typeof autonomyNote === 'string' && autonomyNote.trim() ? autonomyNote.trim() : '';
|
|
204
|
+
return { autonomy, ...(note ? { note } : {}) };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The one plain sentence a text view prints under the loop when its fires will not run for the
|
|
208
|
+
* owner; null when they do, when the server did not say, or when the loop is paused (a paused
|
|
209
|
+
* loop fires nothing, so the gate is not what the reader needs to hear about).
|
|
210
|
+
*/
|
|
211
|
+
function reflectionAutonomyNote(trigger, bin) {
|
|
212
|
+
const verdict = reflectionAutonomy(trigger);
|
|
213
|
+
if (!verdict || verdict.autonomy !== 'suggests_only')
|
|
214
|
+
return null;
|
|
215
|
+
return (verdict.note ??
|
|
216
|
+
`This loop's fires only create an inbox suggestion. Run ${bin} triggers preflight ${trigger.id ?? '<trigger-id>'} to see why.`);
|
|
217
|
+
}
|
|
218
|
+
/** The `--json` fields for the same verdict: absent when unknown, so absence reads as unknown. */
|
|
219
|
+
function reflectionAutonomyFields(trigger) {
|
|
220
|
+
const verdict = reflectionAutonomy(trigger);
|
|
221
|
+
if (!verdict)
|
|
222
|
+
return {};
|
|
223
|
+
return { autonomy: verdict.autonomy, ...(verdict.note ? { note: verdict.note } : {}) };
|
|
224
|
+
}
|
|
116
225
|
/**
|
|
117
226
|
* Build the `POST /api/triggers` body for a fresh self-reflection loop. Uses the
|
|
118
227
|
* proven `scope:agent + kind:schedule + mode:run_only` shape and coalesces so a
|
package/dist/lib/schedule.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/schedule.js
CHANGED
|
@@ -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,
|
|
@@ -28,6 +28,8 @@ function taskActionCommand(action, taskId, targetId, bin = 'skrr', context = {})
|
|
|
28
28
|
request_approval: `tasks approvals create ${task} --type <review|sign_off|deployment>`,
|
|
29
29
|
resolve_approval: `tasks approvals resolve ${task} ${target} --status <approved|rejected>`,
|
|
30
30
|
resolve_task_hold: `tasks move ${task} --status <status>`,
|
|
31
|
+
resume_schedule: `tasks resume-schedule ${task}`,
|
|
32
|
+
retry_schedule: `tasks retry-schedule ${task} --at <time>`,
|
|
31
33
|
retry_task: `tasks retry ${task}`,
|
|
32
34
|
set_execution_mode: `tasks update ${task} --execution-mode <agent|human>`,
|
|
33
35
|
start_task: `tasks start ${task}`,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a schedule command did to the schedule, as sentences.
|
|
3
|
+
*
|
|
4
|
+
* The server already knows these things and reports them as response-only fields
|
|
5
|
+
* (`schedulePaused`, `scheduleChange`, `scheduleResume`, `scheduleElapsedWhileArchived`).
|
|
6
|
+
* The commands printed "Scheduled ..." / "Resumed ..." / "unarchived" regardless, so a
|
|
7
|
+
* person could not tell that a time was recorded but nothing was armed (OSK-21521), that
|
|
8
|
+
* a slot which fell inside their pause was skipped (OSK-21513), or that a time which
|
|
9
|
+
* passed while a Task was archived was dropped rather than run (OSK-21494).
|
|
10
|
+
*/
|
|
11
|
+
type Instant = string | Date | null | undefined;
|
|
12
|
+
export type ScheduleResult = {
|
|
13
|
+
schedulePaused?: {
|
|
14
|
+
pausedAt?: Instant;
|
|
15
|
+
};
|
|
16
|
+
scheduleChange?: {
|
|
17
|
+
changed?: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Lines to print under `Scheduled task ...`. A paused series is told as such, with the one
|
|
23
|
+
* command that un-pauses it, because `schedule` never does.
|
|
24
|
+
*/
|
|
25
|
+
export declare function scheduleResultLines(result: ScheduleResult | null | undefined, taskRef: string, bin?: string): string[];
|
|
26
|
+
export type ResumeResult = {
|
|
27
|
+
scheduleResume?: {
|
|
28
|
+
skippedScheduledFor?: Instant;
|
|
29
|
+
nextRunAt?: Instant;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** What resuming did to the slot that fell inside the pause, or nothing when none did. */
|
|
33
|
+
export declare function resumeResultLines(result: ResumeResult | null | undefined): string[];
|
|
34
|
+
export type UnarchiveResult = {
|
|
35
|
+
scheduleElapsedWhileArchived?: {
|
|
36
|
+
skippedScheduledFor?: Instant;
|
|
37
|
+
nextRunAt?: Instant;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/** What unarchiving did to a schedule that came due while the Task was archived. */
|
|
41
|
+
export declare function unarchiveResultLines(result: UnarchiveResult | null | undefined, taskRef: string, bin?: string): string[];
|
|
42
|
+
/** At archive time: a kept time that will not fire while archived. */
|
|
43
|
+
export declare function archiveResultLines(result: {
|
|
44
|
+
scheduledFor?: Instant;
|
|
45
|
+
} | null | undefined): string[];
|
|
46
|
+
export {};
|