@skrr-ai/cli 0.1.129 → 0.1.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.js +30 -0
- package/dist/commands/agenda/trigger/create.js +9 -1
- package/dist/commands/agents/reflect.d.ts +6 -0
- package/dist/commands/agents/reflect.js +45 -6
- package/dist/commands/agents/schedule-overview.d.ts +17 -0
- package/dist/commands/agents/schedule-overview.js +33 -9
- package/dist/commands/agents/triggers/policy.js +3 -1
- package/dist/commands/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/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/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 +840 -826
- package/oclif.manifest.json +20 -12
- package/package.json +1 -1
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
|
|
@@ -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 {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What a schedule command did to the schedule, as sentences.
|
|
4
|
+
*
|
|
5
|
+
* The server already knows these things and reports them as response-only fields
|
|
6
|
+
* (`schedulePaused`, `scheduleChange`, `scheduleResume`, `scheduleElapsedWhileArchived`).
|
|
7
|
+
* The commands printed "Scheduled ..." / "Resumed ..." / "unarchived" regardless, so a
|
|
8
|
+
* person could not tell that a time was recorded but nothing was armed (OSK-21521), that
|
|
9
|
+
* a slot which fell inside their pause was skipped (OSK-21513), or that a time which
|
|
10
|
+
* passed while a Task was archived was dropped rather than run (OSK-21494).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.scheduleResultLines = scheduleResultLines;
|
|
14
|
+
exports.resumeResultLines = resumeResultLines;
|
|
15
|
+
exports.unarchiveResultLines = unarchiveResultLines;
|
|
16
|
+
exports.archiveResultLines = archiveResultLines;
|
|
17
|
+
function iso(value) {
|
|
18
|
+
if (!value)
|
|
19
|
+
return null;
|
|
20
|
+
const at = value instanceof Date ? value : new Date(value);
|
|
21
|
+
return Number.isNaN(at.getTime()) ? String(value) : at.toISOString();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Lines to print under `Scheduled task ...`. A paused series is told as such, with the one
|
|
25
|
+
* command that un-pauses it, because `schedule` never does.
|
|
26
|
+
*/
|
|
27
|
+
function scheduleResultLines(result, taskRef, bin = 'skrr') {
|
|
28
|
+
const lines = [];
|
|
29
|
+
if (result?.scheduleChange?.changed === false) {
|
|
30
|
+
lines.push('Already scheduled exactly like this; nothing changed.');
|
|
31
|
+
}
|
|
32
|
+
if (result?.schedulePaused) {
|
|
33
|
+
lines.push('The recurring series is PAUSED: this time is recorded, but nothing is armed and nothing will run.', `Resume it with \`${bin} tasks resume-schedule ${taskRef}\` (\`schedule\` never resumes a paused series).`);
|
|
34
|
+
}
|
|
35
|
+
return lines;
|
|
36
|
+
}
|
|
37
|
+
/** What resuming did to the slot that fell inside the pause, or nothing when none did. */
|
|
38
|
+
function resumeResultLines(result) {
|
|
39
|
+
const resume = result?.scheduleResume;
|
|
40
|
+
if (!resume)
|
|
41
|
+
return [];
|
|
42
|
+
const skipped = iso(resume.skippedScheduledFor);
|
|
43
|
+
const next = iso(resume.nextRunAt);
|
|
44
|
+
return [
|
|
45
|
+
`Skipped the run that came due while paused (${skipped}); it is not replayed.`,
|
|
46
|
+
next ? `Next run: ${next}` : 'The series has no further run.',
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/** What unarchiving did to a schedule that came due while the Task was archived. */
|
|
50
|
+
function unarchiveResultLines(result, taskRef, bin = 'skrr') {
|
|
51
|
+
const elapsed = result?.scheduleElapsedWhileArchived;
|
|
52
|
+
if (!elapsed)
|
|
53
|
+
return [];
|
|
54
|
+
const skipped = iso(elapsed.skippedScheduledFor);
|
|
55
|
+
const next = iso(elapsed.nextRunAt);
|
|
56
|
+
return [
|
|
57
|
+
`Its scheduled time (${skipped}) passed while it was archived, so it was skipped, not run.`,
|
|
58
|
+
next
|
|
59
|
+
? `Next run: ${next}`
|
|
60
|
+
: `It is no longer scheduled; set a new time with \`${bin} tasks schedule ${taskRef} --at <time>\`.`,
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
/** At archive time: a kept time that will not fire while archived. */
|
|
64
|
+
function archiveResultLines(result) {
|
|
65
|
+
const kept = iso(result?.scheduledFor);
|
|
66
|
+
return kept
|
|
67
|
+
? [
|
|
68
|
+
`Its scheduled time (${kept}) is kept but will not fire while archived; a time that passes meanwhile is skipped on unarchive.`,
|
|
69
|
+
]
|
|
70
|
+
: [];
|
|
71
|
+
}
|
package/dist/lib/triggers.d.ts
CHANGED
|
@@ -143,6 +143,16 @@ export interface TriggerDefinition {
|
|
|
143
143
|
recordedAt?: string;
|
|
144
144
|
retiredAt?: string;
|
|
145
145
|
};
|
|
146
|
+
/**
|
|
147
|
+
* Server-DERIVED and only present when the list was asked for it (`?include=autonomy`), and
|
|
148
|
+
* only on a self-reflection loop: whether its scheduled fires RUN (`runs`) or stop short of
|
|
149
|
+
* running because the agent's initiative mode applies to them (`suggests_only`, with
|
|
150
|
+
* `autonomyNote` saying why). It is the verdict `triggers preflight` prints at its initiative
|
|
151
|
+
* gate. A server that predates it, or a verdict it could not reach, sends neither field, which
|
|
152
|
+
* means UNKNOWN: a reader must never take an absent `autonomy` for `runs` (OSK-21606).
|
|
153
|
+
*/
|
|
154
|
+
autonomy?: 'runs' | 'suggests_only';
|
|
155
|
+
autonomyNote?: string;
|
|
146
156
|
createdAt?: string;
|
|
147
157
|
updatedAt?: string;
|
|
148
158
|
}
|
|
@@ -457,6 +467,14 @@ export declare function scopeLabel(trigger: TriggerDefinition): string;
|
|
|
457
467
|
* `triggers show` said `Armed: yes` with no time (OSK-15416).
|
|
458
468
|
*/
|
|
459
469
|
export declare function onceDueAt(trigger: TriggerDefinition): string | null;
|
|
470
|
+
/**
|
|
471
|
+
* When a trigger WILL fire next: its scheduled time, and only while it is enabled. A disabled
|
|
472
|
+
* trigger keeps the `schedule.nextRunAt` it had when it was paused, and nothing will ever run
|
|
473
|
+
* at that time — `agents reflect` and `agents schedule-overview` printed it as the next run of
|
|
474
|
+
* a paused loop while `agents upcoming` correctly listed nothing (OSK-21605). Null when
|
|
475
|
+
* nothing is scheduled; never the last fire.
|
|
476
|
+
*/
|
|
477
|
+
export declare function scheduledRunAt(trigger: TriggerDefinition): string | null;
|
|
460
478
|
export declare function nextRunLabel(trigger: TriggerDefinition): string;
|
|
461
479
|
/**
|
|
462
480
|
* Whether a trigger will actually fire — not merely whether someone left it on.
|
package/dist/lib/triggers.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.agentRefEntity = agentRefEntity;
|
|
|
39
39
|
exports.fetchAgentScopedTrigger = fetchAgentScopedTrigger;
|
|
40
40
|
exports.scopeLabel = scopeLabel;
|
|
41
41
|
exports.onceDueAt = onceDueAt;
|
|
42
|
+
exports.scheduledRunAt = scheduledRunAt;
|
|
42
43
|
exports.nextRunLabel = nextRunLabel;
|
|
43
44
|
exports.armedLabel = armedLabel;
|
|
44
45
|
exports.degradedMarker = degradedMarker;
|
|
@@ -1078,12 +1079,21 @@ function onceDueAt(trigger) {
|
|
|
1078
1079
|
const due = once.runAt ?? once.at;
|
|
1079
1080
|
return due ? String(due) : null;
|
|
1080
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* When a trigger WILL fire next: its scheduled time, and only while it is enabled. A disabled
|
|
1084
|
+
* trigger keeps the `schedule.nextRunAt` it had when it was paused, and nothing will ever run
|
|
1085
|
+
* at that time — `agents reflect` and `agents schedule-overview` printed it as the next run of
|
|
1086
|
+
* a paused loop while `agents upcoming` correctly listed nothing (OSK-21605). Null when
|
|
1087
|
+
* nothing is scheduled; never the last fire.
|
|
1088
|
+
*/
|
|
1089
|
+
function scheduledRunAt(trigger) {
|
|
1090
|
+
if (trigger.enabled === false)
|
|
1091
|
+
return null;
|
|
1092
|
+
const next = trigger.schedule?.nextRunAt ?? trigger.selfScheduled?.nextRunAt ?? onceDueAt(trigger);
|
|
1093
|
+
return next ? String(next) : null;
|
|
1094
|
+
}
|
|
1081
1095
|
function nextRunLabel(trigger) {
|
|
1082
|
-
const next = trigger.
|
|
1083
|
-
trigger.selfScheduled?.nextRunAt ??
|
|
1084
|
-
onceDueAt(trigger) ??
|
|
1085
|
-
trigger.audit?.lastFiredAt ??
|
|
1086
|
-
'';
|
|
1096
|
+
const next = scheduledRunAt(trigger) ?? trigger.audit?.lastFiredAt ?? '';
|
|
1087
1097
|
return next ? String(next) : '-';
|
|
1088
1098
|
}
|
|
1089
1099
|
/**
|
|
@@ -1438,7 +1448,7 @@ function renderTriggerSummary(trigger, log, options = {}) {
|
|
|
1438
1448
|
const next = nextRunLabel(trigger);
|
|
1439
1449
|
if (next !== '-') {
|
|
1440
1450
|
// `nextRunLabel` falls back to the last fire when nothing is scheduled; say which it is.
|
|
1441
|
-
const upcoming = Boolean(
|
|
1451
|
+
const upcoming = Boolean(scheduledRunAt(trigger));
|
|
1442
1452
|
log(`${upcoming ? 'Next run:' : 'Last run:'}${next.length > 0 ? ` ${next}` : ''}`);
|
|
1443
1453
|
}
|
|
1444
1454
|
/*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DeviceKeyAlg, type DevicePublicJwk } from './deviceKey.js';
|
|
2
2
|
import { type DeviceKeyCustody, type DeviceKeyHandle } from './deviceKeyHandle.js';
|
|
3
|
+
import { type ExecutorStopReceipt, type ExecutorStopReceiptBody } from './executorStopReceipt.js';
|
|
3
4
|
import { type HardwareKeyCapability, type HardwareKeyRecord } from './deviceKeyHardware.js';
|
|
4
5
|
/**
|
|
5
6
|
* The `cred_envelope.read.decrypt_failed` hint for the device PRIVATE key
|
|
@@ -235,6 +236,14 @@ export declare function signServerRequest(opts: {
|
|
|
235
236
|
refreshToken?: string;
|
|
236
237
|
accessToken?: string;
|
|
237
238
|
}): Promise<string | null>;
|
|
239
|
+
/**
|
|
240
|
+
* Sign an executor stop receipt (OSK-20777 Gate 1) with the device key: the signed proof that a
|
|
241
|
+
* scheduled session stopped, which the server verifies against the key pinned on this machine's row.
|
|
242
|
+
* Returns null when device identity is not active or the key cannot sign right now. NEVER throws: a
|
|
243
|
+
* failed sign emits a telemetry event and the caller sends the response without a receipt, which the
|
|
244
|
+
* server treats as an unproven stop rather than as a failure of the session.
|
|
245
|
+
*/
|
|
246
|
+
export declare function signExecutorStopReceiptWithDeviceKey(body: ExecutorStopReceiptBody): Promise<ExecutorStopReceipt | null>;
|
|
238
247
|
/** @internal Test seams — file path resolvers. */
|
|
239
248
|
export declare function __publicKeyPathForTest(profile: string): string;
|
|
240
249
|
export declare function __privateKeyPathForTest(profile: string): string;
|
|
@@ -51,6 +51,7 @@ __export(deviceIdentityBridge_exports, {
|
|
|
51
51
|
persistPendingHardwareKey: () => persistPendingHardwareKey,
|
|
52
52
|
prepareHardwareKeyCandidate: () => prepareHardwareKeyCandidate,
|
|
53
53
|
resetDeviceIdentity: () => resetDeviceIdentity,
|
|
54
|
+
signExecutorStopReceiptWithDeviceKey: () => signExecutorStopReceiptWithDeviceKey,
|
|
54
55
|
signServerRequest: () => signServerRequest,
|
|
55
56
|
signWithCandidate: () => signWithCandidate
|
|
56
57
|
});
|
|
@@ -60,6 +61,7 @@ var import_node_path = __toESM(require("node:path"), 1);
|
|
|
60
61
|
var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
|
|
61
62
|
var import_deviceKey = require("./deviceKey.js");
|
|
62
63
|
var import_deviceKeyHandle = require("./deviceKeyHandle.js");
|
|
64
|
+
var import_executorStopReceipt = require("./executorStopReceipt.js");
|
|
63
65
|
var import_deviceKeyHardware = require("./deviceKeyHardware.js");
|
|
64
66
|
var import_credentialEnvelopeBridge = require("./credentialEnvelopeBridge.js");
|
|
65
67
|
var import_profileStateDir = require("./profileStateDir.js");
|
|
@@ -827,6 +829,25 @@ async function signServerRequest(opts) {
|
|
|
827
829
|
return null;
|
|
828
830
|
}
|
|
829
831
|
}
|
|
832
|
+
async function signExecutorStopReceiptWithDeviceKey(body) {
|
|
833
|
+
reloadIfRecovered();
|
|
834
|
+
const state = _state;
|
|
835
|
+
if (state.kind !== "active")
|
|
836
|
+
return null;
|
|
837
|
+
try {
|
|
838
|
+
return await (0, import_executorStopReceipt.signExecutorStopReceipt)(state.key, body);
|
|
839
|
+
} catch (err) {
|
|
840
|
+
emit("sign_failed", false, {
|
|
841
|
+
profile: state.profile,
|
|
842
|
+
thumbprint: state.key.thumbprint,
|
|
843
|
+
custody: state.key.custody,
|
|
844
|
+
method: "executor_stop_receipt",
|
|
845
|
+
...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
|
|
846
|
+
message: err?.message ?? "unknown"
|
|
847
|
+
});
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
830
851
|
function __publicKeyPathForTest(profile) {
|
|
831
852
|
return publicKeyPath(profile);
|
|
832
853
|
}
|
|
@@ -1040,6 +1061,7 @@ async function enrollWithServer(opts) {
|
|
|
1040
1061
|
persistPendingHardwareKey,
|
|
1041
1062
|
prepareHardwareKeyCandidate,
|
|
1042
1063
|
resetDeviceIdentity,
|
|
1064
|
+
signExecutorStopReceiptWithDeviceKey,
|
|
1043
1065
|
signServerRequest,
|
|
1044
1066
|
signWithCandidate
|
|
1045
1067
|
});
|
|
@@ -34,7 +34,7 @@ export { AUTH_SERVER_METADATA_PATH, readServerDeviceKeyAlgs, SERVER_DEVICE_KEY_A
|
|
|
34
34
|
export { configureHardwareDeviceKeyHelper, HARDWARE_KEY_HELPER_NAME, HardwareDeviceKeyError, probeHardwareDeviceKey, resolveHardwareDeviceKeyHelper, type HardwareDeviceKeyErrorCode, type HardwareKeyCapability, } from './deviceKeyHardware.js';
|
|
35
35
|
export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUIRED, DPOP_PROOF_TYP, DPOP_PROOF_VERIFY_UNAVAILABLE, accessTokenHash, createDpopKey, isAth, isJwkThumbprint, normalizeDpopHtu, verifyDpopProof, type DpopKey, type DpopProofClaims, type DpopProofRequest, type DpopVerifyFailureReason, type DpopVerifyOptions, type DpopVerifyResult, } from './dpop.js';
|
|
36
36
|
export { initCredEnvelope, initExistingCredEnvelope, initNewIndependentCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
|
|
37
|
-
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, prepareHardwareKeyCandidate, commitDeviceKeyCandidate, signWithCandidate, persistPendingHardwareKey, commitPendingHardwareKey, discardPendingHardwareKey, PENDING_HARDWARE_KEY_MAX_AGE_MS, type DeviceKeyCandidate, type DeviceKeyCandidateResult, type PendingHardwareKeyOutcome, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, describeStoredDeviceKey, DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT, type StoredDeviceKeyDescription, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setUnreadableReloadIntervalForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
37
|
+
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, signExecutorStopReceiptWithDeviceKey, prepareHardwareKeyCandidate, commitDeviceKeyCandidate, signWithCandidate, persistPendingHardwareKey, commitPendingHardwareKey, discardPendingHardwareKey, PENDING_HARDWARE_KEY_MAX_AGE_MS, type DeviceKeyCandidate, type DeviceKeyCandidateResult, type PendingHardwareKeyOutcome, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, describeStoredDeviceKey, DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT, type StoredDeviceKeyDescription, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setUnreadableReloadIntervalForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
38
38
|
export { getMachineUuid, getMachineUuidForProfile, describeMachineIdentity, type MachineIdentityDescription, type MachineIdentityReason, getHardwareMachineUuid, setMachineUuidProfile, getMachineUuidProfile, qualifyMachineUuid, MACHINE_IDENTITY_FILENAME, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
39
39
|
export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
|
|
40
40
|
export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
|
|
@@ -316,6 +316,7 @@ __export(index_exports, {
|
|
|
316
316
|
setMachineUuidProfile: () => import_machineUuid.setMachineUuidProfile,
|
|
317
317
|
shutdownCredEnvelope: () => import_credentialEnvelopeBridge.shutdownCredEnvelope,
|
|
318
318
|
signExecutorStopReceipt: () => import_executorStopReceipt.signExecutorStopReceipt,
|
|
319
|
+
signExecutorStopReceiptWithDeviceKey: () => import_deviceIdentityBridge.signExecutorStopReceiptWithDeviceKey,
|
|
319
320
|
signProofWith: () => import_deviceKey.signProofWith,
|
|
320
321
|
signServerRequest: () => import_deviceIdentityBridge.signServerRequest,
|
|
321
322
|
signWithCandidate: () => import_deviceIdentityBridge.signWithCandidate,
|
|
@@ -686,6 +687,7 @@ var import_independentMachineReservation = require("./independentMachineReservat
|
|
|
686
687
|
setMachineUuidProfile,
|
|
687
688
|
shutdownCredEnvelope,
|
|
688
689
|
signExecutorStopReceipt,
|
|
690
|
+
signExecutorStopReceiptWithDeviceKey,
|
|
689
691
|
signProofWith,
|
|
690
692
|
signServerRequest,
|
|
691
693
|
signWithCandidate,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DeviceKeyAlg, type DevicePublicJwk } from './deviceKey.js';
|
|
2
2
|
import { type DeviceKeyCustody, type DeviceKeyHandle } from './deviceKeyHandle.js';
|
|
3
|
+
import { type ExecutorStopReceipt, type ExecutorStopReceiptBody } from './executorStopReceipt.js';
|
|
3
4
|
import { type HardwareKeyCapability, type HardwareKeyRecord } from './deviceKeyHardware.js';
|
|
4
5
|
/**
|
|
5
6
|
* The `cred_envelope.read.decrypt_failed` hint for the device PRIVATE key
|
|
@@ -235,6 +236,14 @@ export declare function signServerRequest(opts: {
|
|
|
235
236
|
refreshToken?: string;
|
|
236
237
|
accessToken?: string;
|
|
237
238
|
}): Promise<string | null>;
|
|
239
|
+
/**
|
|
240
|
+
* Sign an executor stop receipt (OSK-20777 Gate 1) with the device key: the signed proof that a
|
|
241
|
+
* scheduled session stopped, which the server verifies against the key pinned on this machine's row.
|
|
242
|
+
* Returns null when device identity is not active or the key cannot sign right now. NEVER throws: a
|
|
243
|
+
* failed sign emits a telemetry event and the caller sends the response without a receipt, which the
|
|
244
|
+
* server treats as an unproven stop rather than as a failure of the session.
|
|
245
|
+
*/
|
|
246
|
+
export declare function signExecutorStopReceiptWithDeviceKey(body: ExecutorStopReceiptBody): Promise<ExecutorStopReceipt | null>;
|
|
238
247
|
/** @internal Test seams — file path resolvers. */
|
|
239
248
|
export declare function __publicKeyPathForTest(profile: string): string;
|
|
240
249
|
export declare function __privateKeyPathForTest(profile: string): string;
|
|
@@ -34,6 +34,7 @@ import path from 'node:path';
|
|
|
34
34
|
import lockfile from 'proper-lockfile';
|
|
35
35
|
import { generateDeviceKeyPair, isDevicePublicJwk, jwkThumbprint, signProofWith, } from './deviceKey.js';
|
|
36
36
|
import { softwareEd25519Handle } from './deviceKeyHandle.js';
|
|
37
|
+
import { signExecutorStopReceipt, } from './executorStopReceipt.js';
|
|
37
38
|
import { createHardwareDeviceKey, hardwareDeviceKeyHandle, parseHardwareKeyRecord, probeHardwareDeviceKey, resolveHardwareDeviceKeyHelper, serializeHardwareKeyRecord, HardwareDeviceKeyError, } from './deviceKeyHardware.js';
|
|
38
39
|
import { hasCredEnvelopePathConflict, isCredEnvelopeActive, maybeDecryptOnRead, splitKeyConvergencePolicy, wrapIfActiveOrPassthrough, } from './credentialEnvelopeBridge.js';
|
|
39
40
|
import { consolidateProfileState, profileStateFilePathsToClear, profileStateDir, resolveProfileStateDir, RETIRED_COPY_SUFFIX, } from './profileStateDir.js';
|
|
@@ -1133,6 +1134,33 @@ export async function signServerRequest(opts) {
|
|
|
1133
1134
|
return null;
|
|
1134
1135
|
}
|
|
1135
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Sign an executor stop receipt (OSK-20777 Gate 1) with the device key: the signed proof that a
|
|
1139
|
+
* scheduled session stopped, which the server verifies against the key pinned on this machine's row.
|
|
1140
|
+
* Returns null when device identity is not active or the key cannot sign right now. NEVER throws: a
|
|
1141
|
+
* failed sign emits a telemetry event and the caller sends the response without a receipt, which the
|
|
1142
|
+
* server treats as an unproven stop rather than as a failure of the session.
|
|
1143
|
+
*/
|
|
1144
|
+
export async function signExecutorStopReceiptWithDeviceKey(body) {
|
|
1145
|
+
reloadIfRecovered();
|
|
1146
|
+
const state = _state;
|
|
1147
|
+
if (state.kind !== 'active')
|
|
1148
|
+
return null;
|
|
1149
|
+
try {
|
|
1150
|
+
return await signExecutorStopReceipt(state.key, body);
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
emit('sign_failed', false, {
|
|
1154
|
+
profile: state.profile,
|
|
1155
|
+
thumbprint: state.key.thumbprint,
|
|
1156
|
+
custody: state.key.custody,
|
|
1157
|
+
method: 'executor_stop_receipt',
|
|
1158
|
+
...(err instanceof HardwareDeviceKeyError ? { hardwareFailure: err.code } : {}),
|
|
1159
|
+
message: err?.message ?? 'unknown',
|
|
1160
|
+
});
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1136
1164
|
/** @internal Test seams — file path resolvers. */
|
|
1137
1165
|
export function __publicKeyPathForTest(profile) {
|
|
1138
1166
|
return publicKeyPath(profile);
|
|
@@ -34,7 +34,7 @@ export { AUTH_SERVER_METADATA_PATH, readServerDeviceKeyAlgs, SERVER_DEVICE_KEY_A
|
|
|
34
34
|
export { configureHardwareDeviceKeyHelper, HARDWARE_KEY_HELPER_NAME, HardwareDeviceKeyError, probeHardwareDeviceKey, resolveHardwareDeviceKeyHelper, type HardwareDeviceKeyErrorCode, type HardwareKeyCapability, } from './deviceKeyHardware.js';
|
|
35
35
|
export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUIRED, DPOP_PROOF_TYP, DPOP_PROOF_VERIFY_UNAVAILABLE, accessTokenHash, createDpopKey, isAth, isJwkThumbprint, normalizeDpopHtu, verifyDpopProof, type DpopKey, type DpopProofClaims, type DpopProofRequest, type DpopVerifyFailureReason, type DpopVerifyOptions, type DpopVerifyResult, } from './dpop.js';
|
|
36
36
|
export { initCredEnvelope, initExistingCredEnvelope, initNewIndependentCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
|
|
37
|
-
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, prepareHardwareKeyCandidate, commitDeviceKeyCandidate, signWithCandidate, persistPendingHardwareKey, commitPendingHardwareKey, discardPendingHardwareKey, PENDING_HARDWARE_KEY_MAX_AGE_MS, type DeviceKeyCandidate, type DeviceKeyCandidateResult, type PendingHardwareKeyOutcome, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, describeStoredDeviceKey, DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT, type StoredDeviceKeyDescription, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setUnreadableReloadIntervalForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
37
|
+
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, signExecutorStopReceiptWithDeviceKey, prepareHardwareKeyCandidate, commitDeviceKeyCandidate, signWithCandidate, persistPendingHardwareKey, commitPendingHardwareKey, discardPendingHardwareKey, PENDING_HARDWARE_KEY_MAX_AGE_MS, type DeviceKeyCandidate, type DeviceKeyCandidateResult, type PendingHardwareKeyOutcome, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, describeStoredDeviceKey, DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT, type StoredDeviceKeyDescription, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setUnreadableReloadIntervalForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
38
38
|
export { getMachineUuid, getMachineUuidForProfile, describeMachineIdentity, type MachineIdentityDescription, type MachineIdentityReason, getHardwareMachineUuid, setMachineUuidProfile, getMachineUuidProfile, qualifyMachineUuid, MACHINE_IDENTITY_FILENAME, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
39
39
|
export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
|
|
40
40
|
export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
|