@skrr-ai/cli 0.1.128 → 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.d.ts +12 -0
- package/dist/base-command.js +65 -0
- package/dist/commands/agenda/trigger/create.js +9 -1
- package/dist/commands/agenda/trigger/list.d.ts +2 -1
- package/dist/commands/agenda/trigger/list.js +55 -6
- package/dist/commands/agents/create.js +3 -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/browser/persistent-profile.d.ts +22 -0
- package/dist/commands/browser/persistent-profile.js +46 -0
- package/dist/commands/commitments/create.js +3 -1
- package/dist/commands/commitments/effective-policy.js +6 -0
- package/dist/commands/commitments/explain.js +10 -1
- package/dist/commands/commitments/plan.d.ts +14 -0
- package/dist/commands/commitments/plan.js +32 -0
- package/dist/commands/commitments/preflight.d.ts +1 -1
- package/dist/commands/commitments/preflight.js +12 -4
- package/dist/commands/commitments/show.d.ts +9 -0
- package/dist/commands/commitments/show.js +14 -1
- package/dist/commands/compass/revisions.d.ts +6 -2
- package/dist/commands/compass/revisions.js +13 -5
- package/dist/commands/compass/tactic.d.ts +5 -0
- package/dist/commands/compass/tactic.js +14 -0
- package/dist/commands/followups/plans/create.js +1 -1
- package/dist/commands/harnesses/list.js +1 -1
- package/dist/commands/northstars/create.js +6 -1
- package/dist/commands/northstars/governance/configure.js +1 -1
- package/dist/commands/northstars/intents/decide.js +2 -3
- package/dist/commands/northstars/list.js +1 -1
- package/dist/commands/northstars/review-now.js +5 -0
- package/dist/commands/northstars/reviews.js +15 -3
- package/dist/commands/northstars/show.js +1 -1
- package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
- package/dist/commands/spaces/auto-executor/runs.js +18 -1
- package/dist/commands/spaces/heartbeats/create.js +7 -5
- package/dist/commands/spaces/heartbeats/status.js +10 -0
- package/dist/commands/spaces/heartbeats/update.js +5 -3
- package/dist/commands/store/artifacts/releases.d.ts +20 -0
- package/dist/commands/store/artifacts/releases.js +85 -0
- package/dist/commands/store/library.d.ts +14 -0
- package/dist/commands/store/library.js +71 -0
- package/dist/commands/tasks/archive.js +6 -0
- package/dist/commands/tasks/list.d.ts +48 -0
- package/dist/commands/tasks/list.js +141 -9
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/remind.js +23 -11
- package/dist/commands/tasks/resume-schedule.js +4 -1
- package/dist/commands/tasks/retry-schedule.js +6 -3
- package/dist/commands/tasks/schedule.js +35 -8
- package/dist/commands/tasks/self-schedule.d.ts +56 -0
- package/dist/commands/tasks/self-schedule.js +179 -21
- package/dist/commands/tasks/show.js +4 -0
- package/dist/lib/agent-trigger-listing.d.ts +26 -0
- package/dist/lib/agent-trigger-listing.js +63 -0
- package/dist/lib/agentic-stream.js +138 -41
- package/dist/lib/commitment-ledgers.js +3 -1
- package/dist/lib/commitments.d.ts +24 -0
- package/dist/lib/commitments.js +64 -4
- package/dist/lib/compasses.d.ts +2 -0
- package/dist/lib/iso-instant.d.ts +52 -0
- package/dist/lib/iso-instant.js +162 -0
- package/dist/lib/northstars.d.ts +87 -4
- package/dist/lib/northstars.js +226 -94
- package/dist/lib/proposals.js +6 -2
- package/dist/lib/reflection.d.ts +58 -0
- package/dist/lib/reflection.js +115 -6
- package/dist/lib/store-artifact-releases.d.ts +39 -0
- package/dist/lib/store-artifact-releases.js +12 -0
- package/dist/lib/store-library.d.ts +37 -0
- package/dist/lib/store-library.js +12 -0
- package/dist/lib/task-calendar.d.ts +19 -0
- package/dist/lib/task-calendar.js +72 -0
- package/dist/lib/task-operations.js +2 -0
- package/dist/lib/task-recurrence-outcome.js +3 -1
- package/dist/lib/task-recurrence-summary.d.ts +15 -0
- package/dist/lib/task-recurrence-summary.js +228 -0
- package/dist/lib/task-reminder.d.ts +28 -0
- package/dist/lib/task-reminder.js +50 -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 +31 -2
- package/dist/lib/triggers.js +38 -18
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
- 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/executorStopReceipt.d.ts +123 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +19 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
- 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/executorStopReceipt.d.ts +123 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6772 -6675
- package/oclif.manifest.json +282 -40
- package/package.json +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reminderReceiptLines = reminderReceiptLines;
|
|
4
|
+
/**
|
|
5
|
+
* The receipt `skrr tasks remind` prints (OSK-21505).
|
|
6
|
+
*
|
|
7
|
+
* It used to say "Reminder set for <title> at <toLocaleString()>." for every outcome: a
|
|
8
|
+
* local time with no zone and a doubled full stop, no id to cancel with, and the same
|
|
9
|
+
* words when the server had silently MOVED the Task's one existing reminder ("two
|
|
10
|
+
* identical reminders are one reminder") - so a person who set "2 days before" and then
|
|
11
|
+
* "1 hour before" lost the first and was never told.
|
|
12
|
+
*
|
|
13
|
+
* The server now answers `reminderOutcome` beside the trigger (additive; an older server
|
|
14
|
+
* omits it, and the receipt falls back to the plain "set" wording rather than guessing).
|
|
15
|
+
*/
|
|
16
|
+
const iso_instant_1 = require("./iso-instant");
|
|
17
|
+
function readInstant(value) {
|
|
18
|
+
if (typeof value !== 'string' && !(value instanceof Date))
|
|
19
|
+
return null;
|
|
20
|
+
const date = new Date(value);
|
|
21
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The lines to print after a successful `POST /api/triggers`.
|
|
25
|
+
*
|
|
26
|
+
* `requested` is the instant the user asked for; the stored `once.runAt` wins when the
|
|
27
|
+
* server returned one, because that is the time that will fire.
|
|
28
|
+
*/
|
|
29
|
+
function reminderReceiptLines(input) {
|
|
30
|
+
const { title, trigger, requested, bin = 'skrr', timeZone } = input;
|
|
31
|
+
const stored = readInstant(trigger?.once?.runAt) ?? requested;
|
|
32
|
+
const at = (0, iso_instant_1.describeInstant)(stored, timeZone);
|
|
33
|
+
const outcome = trigger?.reminderOutcome;
|
|
34
|
+
const lines = [];
|
|
35
|
+
if (outcome?.reused && outcome.moved) {
|
|
36
|
+
const previous = readInstant(outcome.previousRunAt);
|
|
37
|
+
lines.push(`Reminder moved for ${title} to ${at}${previous ? ` (was ${(0, iso_instant_1.describeInstant)(previous, timeZone)})` : ''}`);
|
|
38
|
+
lines.push(' A Task keeps one reminder per wording, so the earlier time no longer fires.');
|
|
39
|
+
}
|
|
40
|
+
else if (outcome?.reused) {
|
|
41
|
+
lines.push(`Reminder already set for ${title} at ${at}; nothing changed`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
lines.push(`Reminder set for ${title} at ${at}`);
|
|
45
|
+
}
|
|
46
|
+
if (trigger?.id) {
|
|
47
|
+
lines.push(` Reminder id: ${trigger.id} Cancel it with: ${bin} followups cancel ${trigger.id}`);
|
|
48
|
+
}
|
|
49
|
+
return lines;
|
|
50
|
+
}
|
|
@@ -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
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { type EmbeddedActionRef, type TriggerIntentUnsupportedItem, type TriggerPreflightReport, type TriggerSensorConfig } from '@skrr-ai/data-provider';
|
|
2
2
|
export declare const TRIGGER_KINDS: readonly ["schedule", "once", "event", "stochastic", "self_scheduled", "on_demand", "form", "chat_message"];
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The scope kinds the trigger endpoints accept. NOT a list of its own: `@skrr-ai/data-provider` owns it
|
|
5
|
+
* (`TRIGGER_API_SCOPE_KINDS`) and the API route reads the same array. The hand-written copy that stood here
|
|
6
|
+
* lacked `commitment_watcher` and `space_cycle`, so `triggers list --scope commitment_watcher` was refused locally
|
|
7
|
+
* for a scope the server serves (OSK-21518). `trigger-scope-kinds-parity.spec.ts` pins it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TRIGGER_SCOPE_KINDS: readonly ["agent", "group_chat_subscriber", "space_oracle", "space_subscriber", "key_result_watcher", "task_watcher", "goal_watcher", "commitment_watcher", "entity_reminder", "space_cycle"];
|
|
4
10
|
/**
|
|
5
11
|
* Executor modes this CLI will let you author.
|
|
6
12
|
*
|
|
@@ -137,6 +143,16 @@ export interface TriggerDefinition {
|
|
|
137
143
|
recordedAt?: string;
|
|
138
144
|
retiredAt?: string;
|
|
139
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;
|
|
140
156
|
createdAt?: string;
|
|
141
157
|
updatedAt?: string;
|
|
142
158
|
}
|
|
@@ -451,6 +467,14 @@ export declare function scopeLabel(trigger: TriggerDefinition): string;
|
|
|
451
467
|
* `triggers show` said `Armed: yes` with no time (OSK-15416).
|
|
452
468
|
*/
|
|
453
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;
|
|
454
478
|
export declare function nextRunLabel(trigger: TriggerDefinition): string;
|
|
455
479
|
/**
|
|
456
480
|
* Whether a trigger will actually fire — not merely whether someone left it on.
|
|
@@ -508,7 +532,12 @@ export declare function formatDuplicateSubscriptionWarning(trigger: unknown): {
|
|
|
508
532
|
* "armed" when it will fire and "inert" until enabled — so a freshly-created
|
|
509
533
|
* trigger did not plainly confirm it is live.
|
|
510
534
|
*/
|
|
511
|
-
export declare function armedStateLabel(enabled: boolean | undefined, lastResult?: string | null
|
|
535
|
+
export declare function armedStateLabel(enabled: boolean | undefined, lastResult?: string | null,
|
|
536
|
+
/** Pass for a `once` trigger: how often its single fire ran. A spent one-shot is not waiting to be enabled (OSK-21509). */
|
|
537
|
+
oneShot?: {
|
|
538
|
+
fires?: number;
|
|
539
|
+
failures?: number;
|
|
540
|
+
} | null): string;
|
|
512
541
|
/**
|
|
513
542
|
* Human phrasing for an effect trigger's write mode (OSK-4886).
|
|
514
543
|
*
|
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;
|
|
@@ -66,16 +67,13 @@ exports.TRIGGER_KINDS = [
|
|
|
66
67
|
'form',
|
|
67
68
|
'chat_message',
|
|
68
69
|
];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
'goal_watcher',
|
|
77
|
-
'entity_reminder',
|
|
78
|
-
];
|
|
70
|
+
/**
|
|
71
|
+
* The scope kinds the trigger endpoints accept. NOT a list of its own: `@skrr-ai/data-provider` owns it
|
|
72
|
+
* (`TRIGGER_API_SCOPE_KINDS`) and the API route reads the same array. The hand-written copy that stood here
|
|
73
|
+
* lacked `commitment_watcher` and `space_cycle`, so `triggers list --scope commitment_watcher` was refused locally
|
|
74
|
+
* for a scope the server serves (OSK-21518). `trigger-scope-kinds-parity.spec.ts` pins it.
|
|
75
|
+
*/
|
|
76
|
+
exports.TRIGGER_SCOPE_KINDS = data_provider_1.TRIGGER_API_SCOPE_KINDS;
|
|
79
77
|
/**
|
|
80
78
|
* Executor modes this CLI will let you author.
|
|
81
79
|
*
|
|
@@ -346,12 +344,15 @@ exports.SCOPE_IMPLIED_REF_KEY = {
|
|
|
346
344
|
space_oracle: 'spaceId',
|
|
347
345
|
goal_watcher: 'goalId',
|
|
348
346
|
group_chat_subscriber: 'groupChatId',
|
|
347
|
+
space_cycle: 'spaceId',
|
|
349
348
|
};
|
|
350
349
|
/** Ref keys each multi-ref scope expects, so the error can name them. */
|
|
351
350
|
const SCOPE_EXPECTED_REF_KEYS = {
|
|
352
351
|
task_watcher: ['taskId', 'watcherAgentId'],
|
|
353
352
|
key_result_watcher: ['keyResultId', 'watcherAgentId'],
|
|
354
353
|
space_subscriber: ['spaceId', 'agentId', 'subscriberUserId'],
|
|
354
|
+
// `commitmentId` is the one the resolver requires; it also reads optional agentId/goalId/spaceId/... refs.
|
|
355
|
+
commitment_watcher: ['commitmentId'],
|
|
355
356
|
};
|
|
356
357
|
function parseKeyValueFlags(values, flagName, scopeKind) {
|
|
357
358
|
const refs = {};
|
|
@@ -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
|
/**
|
|
@@ -1241,7 +1251,15 @@ function formatDuplicateSubscriptionWarning(trigger) {
|
|
|
1241
1251
|
* "armed" when it will fire and "inert" until enabled — so a freshly-created
|
|
1242
1252
|
* trigger did not plainly confirm it is live.
|
|
1243
1253
|
*/
|
|
1244
|
-
function armedStateLabel(enabled, lastResult
|
|
1254
|
+
function armedStateLabel(enabled, lastResult,
|
|
1255
|
+
/** Pass for a `once` trigger: how often its single fire ran. A spent one-shot is not waiting to be enabled (OSK-21509). */
|
|
1256
|
+
oneShot) {
|
|
1257
|
+
if (enabled === false && oneShot && (oneShot.fires ?? 0) > 0) {
|
|
1258
|
+
return 'no — already fired (a one-shot trigger is spent once it fires)';
|
|
1259
|
+
}
|
|
1260
|
+
if (enabled === false && oneShot && (oneShot.failures ?? 0) > 0) {
|
|
1261
|
+
return 'no — its one fire already ran and did not succeed (a one-shot trigger is spent; see `skrr triggers audit`)';
|
|
1262
|
+
}
|
|
1245
1263
|
if (enabled === false)
|
|
1246
1264
|
return 'no — inert until enabled';
|
|
1247
1265
|
// "Will fire" is the row's intent. A platform-managed row whose owner is fenced (Always-On off) ticks on
|
|
@@ -1348,7 +1366,9 @@ function fireRefusalLines(metadata) {
|
|
|
1348
1366
|
function renderTriggerSummary(trigger, log, options = {}) {
|
|
1349
1367
|
log(`ID: ${trigger.id ?? '-'}`);
|
|
1350
1368
|
log(`Kind: ${trigger.kind ?? '-'}`);
|
|
1351
|
-
log(`Armed: ${armedStateLabel(trigger.enabled, trigger.audit?.lastResult
|
|
1369
|
+
log(`Armed: ${armedStateLabel(trigger.enabled, trigger.audit?.lastResult, trigger.kind === 'once'
|
|
1370
|
+
? { fires: trigger.audit?.fireCount, failures: trigger.audit?.failureCount }
|
|
1371
|
+
: undefined)}`);
|
|
1352
1372
|
log(`Scope: ${scopeLabel(trigger)}`);
|
|
1353
1373
|
/*
|
|
1354
1374
|
* Say when the row is not yours to change.
|
|
@@ -1428,7 +1448,7 @@ function renderTriggerSummary(trigger, log, options = {}) {
|
|
|
1428
1448
|
const next = nextRunLabel(trigger);
|
|
1429
1449
|
if (next !== '-') {
|
|
1430
1450
|
// `nextRunLabel` falls back to the last fire when nothing is scheduled; say which it is.
|
|
1431
|
-
const upcoming = Boolean(
|
|
1451
|
+
const upcoming = Boolean(scheduledRunAt(trigger));
|
|
1432
1452
|
log(`${upcoming ? 'Next run:' : 'Last run:'}${next.length > 0 ? ` ${next}` : ''}`);
|
|
1433
1453
|
}
|
|
1434
1454
|
/*
|
|
@@ -69,6 +69,7 @@ const SESSION_TURN_DIFF_LIVE = "session_turn_diff_live_v1";
|
|
|
69
69
|
const WORKSPACE_ISOLATION_PROBE = "workspace_isolation_probe_v1";
|
|
70
70
|
const TOOL_RESPONSE_OUTBOX = "tool_response_outbox_v1";
|
|
71
71
|
const SESSION_STEER = "session_steer_v1";
|
|
72
|
+
const QUESTION_ACK = "question_ack_v1";
|
|
72
73
|
const CONNECTION = [
|
|
73
74
|
toServer("daemon:register", "The daemon introduces itself: identity, version, capabilities, sessions it holds.", {
|
|
74
75
|
delivery: "acked",
|
|
@@ -226,7 +227,16 @@ const SESSIONS = [
|
|
|
226
227
|
delivery: "acked",
|
|
227
228
|
answeredBy: "daemon:claude:question:response"
|
|
228
229
|
}),
|
|
229
|
-
|
|
230
|
+
// A daemon that announces `question_ack_v1` says whether it TOOK the answer: the
|
|
231
|
+
// question was still pending and the harness has it ('delivered'), or the daemon had
|
|
232
|
+
// already timed out or been cancelled ('not_pending'). Without the capability the
|
|
233
|
+
// worker settles an answer at the relay handoff, as it always did.
|
|
234
|
+
toDaemon("daemon:claude:question:response", "A person's answer to a question.", {
|
|
235
|
+
delivery: "acked",
|
|
236
|
+
answeredBy: "daemon:claude:question:ack",
|
|
237
|
+
capability: QUESTION_ACK
|
|
238
|
+
}),
|
|
239
|
+
toServer("daemon:claude:question:ack", "Whether this daemon took a person's answer to a question, or had already stopped waiting.", { capability: QUESTION_ACK, payload: { kind: "zod", schema: "daemonQuestionAckSchema" } })
|
|
230
240
|
];
|
|
231
241
|
const TOOLS = [
|
|
232
242
|
toDaemon("daemon:tool:request", "Run one tool call on this machine.", {
|
|
@@ -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
|
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* executorStopReceipt.ts — a daemon-signed proof that a scheduled session stopped.
|
|
3
|
+
*
|
|
4
|
+
* OSK-20777, Gate 1 (docs/architecture/scheduled-publication-custody-enrollment-2026-11-01.md).
|
|
5
|
+
* Today the executor's terminal proof is the daemon reporting about itself over its own socket: the
|
|
6
|
+
* dispatch tuple is checked for consistency but nothing is signed, nothing covers descendant
|
|
7
|
+
* processes, and nothing says what was erased. A receipt is that report, signed with the device key
|
|
8
|
+
* the server already pins on the daemon's row, so a settlement that depends on it depends on the
|
|
9
|
+
* machine and not on whoever reached the socket.
|
|
10
|
+
*
|
|
11
|
+
* No new cryptography. A receipt is a DPoP-shaped device proof (`deviceKey.ts`) under its OWN header
|
|
12
|
+
* `typ`, so the two can never stand in for each other: a request proof fails here with `bad_typ`, and a
|
|
13
|
+
* receipt fails the request verifier the same way. The proof's `ath` commits to the SHA-256 of the
|
|
14
|
+
* canonical receipt body, its `htu` names the dispatch, and the pin decides which key may sign it.
|
|
15
|
+
*
|
|
16
|
+
* Verification is deliberately strict and returns a reason, never a boolean:
|
|
17
|
+
* - a pin is REQUIRED (a receipt with no key to compare against has no authority);
|
|
18
|
+
* - the algorithm admitted is the algorithm of the pin, never a list, so a receipt cannot choose a
|
|
19
|
+
* weaker algorithm than the device enrolled with;
|
|
20
|
+
* - the body is a closed shape with bounded fields;
|
|
21
|
+
* - every field the caller expects (the dispatch the server issued) must match exactly;
|
|
22
|
+
* - a descendant that survived is `not_quiescent`, not a valid receipt with a footnote;
|
|
23
|
+
* - erasure is claimed only for the one scope the owner allowed, `session_temp`.
|
|
24
|
+
*
|
|
25
|
+
* Freshness is the caller's decision (`maxAgeSeconds`). A receipt can legitimately arrive hours after it
|
|
26
|
+
* was signed, replayed from the daemon's response WAL after a reconnect, so it has no short window of its
|
|
27
|
+
* own. Replay protection is the dispatch binding: the server accepts one receipt per `dispatchId`.
|
|
28
|
+
*/
|
|
29
|
+
import { type DeviceKeyAlg, type DeviceKeySigner, type DevicePublicJwk, type VerifyFailureReason } from './deviceKey.js';
|
|
30
|
+
/** Header `typ` of an executor stop receipt. Distinct from `DEVICE_PROOF_TYP` on purpose. */
|
|
31
|
+
export declare const EXECUTOR_STOP_RECEIPT_TYP = "skrr-executor-stop-receipt+jwt";
|
|
32
|
+
/** What the daemon may claim to have erased. One scope, decided by the owner (2026-11-01). */
|
|
33
|
+
export declare const EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES: readonly ["session_temp"];
|
|
34
|
+
export type ExecutorStopReceiptErasureScope = (typeof EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES)[number];
|
|
35
|
+
/** Default ceiling on a receipt's age, for a caller that does not decide: one day. */
|
|
36
|
+
export declare const EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS: number;
|
|
37
|
+
export type ExecutorStopReceiptStatus = 'completed' | 'failed' | 'cancelled';
|
|
38
|
+
export interface ExecutorStopReceiptBody {
|
|
39
|
+
version: 1;
|
|
40
|
+
daemonId: string;
|
|
41
|
+
/** The scheduled Run (or Source) the dispatch belongs to. */
|
|
42
|
+
sourceId: string;
|
|
43
|
+
sessionId: string;
|
|
44
|
+
requestId: string;
|
|
45
|
+
dispatchId: string;
|
|
46
|
+
status: ExecutorStopReceiptStatus;
|
|
47
|
+
/** ISO-8601, when the session's process tree was last observed stopped. */
|
|
48
|
+
stoppedAt: string;
|
|
49
|
+
/** Counted after the stop, not assumed. `observed === stopped + survivors`. */
|
|
50
|
+
descendants: {
|
|
51
|
+
observed: number;
|
|
52
|
+
stopped: number;
|
|
53
|
+
survivors: number;
|
|
54
|
+
};
|
|
55
|
+
/** `performed: false` carries no scope: nothing was erased and nothing is claimed. */
|
|
56
|
+
erasure: {
|
|
57
|
+
performed: false;
|
|
58
|
+
} | {
|
|
59
|
+
performed: true;
|
|
60
|
+
scope: ExecutorStopReceiptErasureScope;
|
|
61
|
+
artifacts: number;
|
|
62
|
+
bytes: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface ExecutorStopReceipt {
|
|
66
|
+
body: ExecutorStopReceiptBody;
|
|
67
|
+
/** The device-key proof (JWS compact) that commits to `body`. */
|
|
68
|
+
proof: string;
|
|
69
|
+
}
|
|
70
|
+
export type ExecutorStopReceiptFailureReason = VerifyFailureReason | 'bad_receipt' | 'no_pin' | 'binding_mismatch' | 'not_quiescent';
|
|
71
|
+
export type VerifyExecutorStopReceiptResult = {
|
|
72
|
+
valid: true;
|
|
73
|
+
body: ExecutorStopReceiptBody;
|
|
74
|
+
alg: DeviceKeyAlg;
|
|
75
|
+
thumbprint: string;
|
|
76
|
+
signedAtSeconds: number;
|
|
77
|
+
} | {
|
|
78
|
+
valid: false;
|
|
79
|
+
reason: ExecutorStopReceiptFailureReason;
|
|
80
|
+
};
|
|
81
|
+
declare const BINDING_KEYS: readonly ["daemonId", "sourceId", "sessionId", "requestId", "dispatchId"];
|
|
82
|
+
/**
|
|
83
|
+
* Stable JSON: object keys sorted at every level, no whitespace. The signer and the verifier must
|
|
84
|
+
* produce the same bytes from the same body, whatever order a transport reordered its keys into.
|
|
85
|
+
*/
|
|
86
|
+
export declare function canonicalReceiptJson(value: unknown): string;
|
|
87
|
+
/** The URI a receipt's proof names. Reserved `.invalid` host: it can never be a real request target. */
|
|
88
|
+
export declare function executorStopReceiptHtu(dispatchId: string): string;
|
|
89
|
+
/** Shape check only. Returns the body as typed, or null when it is not exactly a v1 receipt body. */
|
|
90
|
+
export declare function parseExecutorStopReceiptBody(value: unknown): ExecutorStopReceiptBody | null;
|
|
91
|
+
export interface SignExecutorStopReceiptOptions {
|
|
92
|
+
/** Test hooks; production leaves both unset. */
|
|
93
|
+
iat?: number;
|
|
94
|
+
jti?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Sign a receipt with any `DeviceKeySigner` (a software key, or a hardware handle). Throws on a body
|
|
98
|
+
* that is not exactly a v1 receipt body, so a daemon bug cannot produce a signed receipt the server
|
|
99
|
+
* would only reject later.
|
|
100
|
+
*/
|
|
101
|
+
export declare function signExecutorStopReceipt(signer: DeviceKeySigner, body: ExecutorStopReceiptBody, options?: SignExecutorStopReceiptOptions): Promise<ExecutorStopReceipt>;
|
|
102
|
+
export interface VerifyExecutorStopReceiptOptions {
|
|
103
|
+
/** The key pinned on the daemon's row. REQUIRED: without it nothing authorizes the signature. */
|
|
104
|
+
pinnedPublicJwk: DevicePublicJwk | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* What the server issued for this dispatch. Every key given must equal the receipt's, so a receipt for
|
|
107
|
+
* another daemon, Run, session, request or dispatch is `binding_mismatch`.
|
|
108
|
+
*/
|
|
109
|
+
expected?: Partial<Pick<ExecutorStopReceiptBody, (typeof BINDING_KEYS)[number]>>;
|
|
110
|
+
/** Ceiling on the receipt's age in seconds. Defaults to one day. */
|
|
111
|
+
maxAgeSeconds?: number;
|
|
112
|
+
/** Tolerated clock skew in seconds. Defaults to the proof verifier's. */
|
|
113
|
+
clockSkewSeconds?: number;
|
|
114
|
+
/** Testing hook. */
|
|
115
|
+
now?: number;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Verify a receipt against the pinned key. Order matters: shape, then signature and pin, then the
|
|
119
|
+
* binding to the dispatch, then what the body claims. A forged receipt is reported as the forgery it
|
|
120
|
+
* is (`sig_invalid`, `pin_mismatch`, `ath_mismatch`), not as a quiescence failure.
|
|
121
|
+
*/
|
|
122
|
+
export declare function verifyExecutorStopReceipt(receipt: unknown, options: VerifyExecutorStopReceiptOptions): VerifyExecutorStopReceiptResult;
|
|
123
|
+
export {};
|