@skrr-ai/cli 0.1.23 → 0.1.25
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/bin/run.js +54 -1
- package/dist/base-command.d.ts +4 -4
- package/dist/base-command.js +81 -6
- package/dist/commands/harnesses/leases/list.js +1 -1
- package/dist/commands/harnesses/products.js +1 -0
- package/dist/commands/machines/dedicated/create.d.ts +18 -0
- package/dist/commands/machines/dedicated/create.js +90 -0
- package/dist/commands/machines/dedicated/destroy.d.ts +14 -0
- package/dist/commands/machines/dedicated/destroy.js +56 -0
- package/dist/commands/machines/dedicated/grow.d.ts +13 -0
- package/dist/commands/machines/dedicated/grow.js +46 -0
- package/dist/commands/machines/dedicated/health-check.d.ts +12 -0
- package/dist/commands/machines/dedicated/health-check.js +42 -0
- package/dist/commands/machines/dedicated/incident-close.d.ts +12 -0
- package/dist/commands/machines/dedicated/incident-close.js +42 -0
- package/dist/commands/machines/dedicated/index.d.ts +6 -0
- package/dist/commands/machines/dedicated/index.js +39 -0
- package/dist/commands/machines/dedicated/list.d.ts +11 -0
- package/dist/commands/machines/dedicated/list.js +58 -0
- package/dist/commands/machines/dedicated/restart.d.ts +12 -0
- package/dist/commands/machines/dedicated/restart.js +42 -0
- package/dist/commands/machines/dedicated/show.d.ts +12 -0
- package/dist/commands/machines/dedicated/show.js +58 -0
- package/dist/commands/machines/dedicated/snapshot.d.ts +12 -0
- package/dist/commands/machines/dedicated/snapshot.js +42 -0
- package/dist/commands/machines/dedicated/start.d.ts +12 -0
- package/dist/commands/machines/dedicated/start.js +42 -0
- package/dist/commands/machines/dedicated/stop.d.ts +12 -0
- package/dist/commands/machines/dedicated/stop.js +42 -0
- package/dist/commands/machines/hosted/index.js +4 -1
- package/dist/commands/spaces/auto-executor/preview.d.ts +8 -0
- package/dist/commands/spaces/auto-executor/preview.js +41 -0
- package/dist/commands/spaces/task-rollups.d.ts +14 -0
- package/dist/commands/spaces/task-rollups.js +66 -0
- package/dist/commands/spaces/update.js +16 -12
- package/dist/commands/spaces/work-sync/reconcile.d.ts +38 -0
- package/dist/commands/spaces/work-sync/reconcile.js +90 -0
- package/dist/commands/spaces/work-sync/telemetry.d.ts +29 -0
- package/dist/commands/spaces/work-sync/telemetry.js +118 -0
- package/dist/commands/tasks/activity.js +5 -1
- package/dist/commands/tasks/assign.js +1 -1
- package/dist/commands/tasks/complete.d.ts +5 -1
- package/dist/commands/tasks/complete.js +17 -3
- package/dist/commands/tasks/create.d.ts +1 -0
- package/dist/commands/tasks/create.js +17 -4
- package/dist/commands/tasks/deliverable/add.js +22 -4
- package/dist/commands/tasks/deliverable/list.js +23 -2
- package/dist/commands/tasks/events/append.js +31 -3
- package/dist/commands/tasks/expectations/assess.d.ts +1 -1
- package/dist/commands/tasks/expectations/assess.js +33 -7
- package/dist/commands/tasks/expectations.js +110 -10
- package/dist/commands/tasks/output.d.ts +0 -1
- package/dist/commands/tasks/output.js +21 -1
- package/dist/commands/tasks/report.js +3 -1
- package/dist/commands/tasks/result/show.js +20 -2
- package/dist/commands/tasks/resume/save.js +11 -1
- package/dist/commands/tasks/self-schedule.js +7 -4
- package/dist/commands/tasks/show.js +19 -0
- package/dist/commands/tasks/timeline.js +9 -1
- package/dist/commands/tasks/updates/add.js +23 -2
- package/dist/lib/api-fetch.js +9 -1
- package/dist/lib/assignee-resolver.d.ts +7 -6
- package/dist/lib/daemonBroker.d.ts +20 -0
- package/dist/lib/daemonBroker.js +58 -17
- package/dist/lib/dedicated-machines.d.ts +66 -0
- package/dist/lib/dedicated-machines.js +161 -0
- package/dist/lib/harnesses.d.ts +5 -0
- package/dist/lib/harnesses.js +5 -2
- package/dist/lib/idempotency-scope.d.ts +10 -0
- package/dist/lib/idempotency-scope.js +16 -0
- package/dist/lib/node-adapter.d.ts +1 -1
- package/dist/lib/node-adapter.js +12 -10
- package/dist/lib/task-legacy.d.ts +24 -0
- package/dist/lib/task-legacy.js +48 -0
- package/dist/lib/tasks.d.ts +32 -0
- package/dist/lib/tasks.js +47 -1
- package/dist/lib/triggers.js +7 -0
- package/dist/lib/work-sync.d.ts +105 -0
- package/dist/lib/work-sync.js +29 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +71 -21
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +71 -21
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +2141 -2061
- package/oclif.manifest.json +15661 -14585
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type HarnessLease } from './harnesses';
|
|
2
|
+
export type DedicatedLeaseAction = 'start' | 'stop' | 'restart' | 'snapshot' | 'health-check';
|
|
3
|
+
export declare const DEDICATED_LEASE_ACTIONS: DedicatedLeaseAction[];
|
|
4
|
+
export interface DedicatedRuntimeScope {
|
|
5
|
+
workspaceId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CreateDedicatedRuntimeInput extends DedicatedRuntimeScope {
|
|
8
|
+
requestId: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
sizePreset: string;
|
|
11
|
+
regionClass?: string;
|
|
12
|
+
storageGb?: number;
|
|
13
|
+
retentionClass?: string;
|
|
14
|
+
backupProfile?: 'crash-consistent' | 'application-consistent';
|
|
15
|
+
monthlySpendingLimitCents: number;
|
|
16
|
+
repositoryRef?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DedicatedRuntimeResponse {
|
|
19
|
+
lease?: HarnessLease;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Flags in, request body out — and the point of it being a function is the
|
|
23
|
+
* OMISSION.
|
|
24
|
+
*
|
|
25
|
+
* A field the user did not set must be ABSENT from the body, not present as
|
|
26
|
+
* `undefined`, because the server reaches its own default through
|
|
27
|
+
* `input.x || <default>`. Restating those defaults on the client looks
|
|
28
|
+
* harmless and is not: `--storage-gb` shipped with a client default of 100
|
|
29
|
+
* against a product default of 50, so a user who never chose a disk size was
|
|
30
|
+
* billed for twice the documented one and `workspaceDefaultGb` was unreachable
|
|
31
|
+
* through the CLI. The three neighbouring flags happened to agree, which only
|
|
32
|
+
* means the same bug was invisible in them.
|
|
33
|
+
*/
|
|
34
|
+
export declare function dedicatedRuntimeCreateInput(input: {
|
|
35
|
+
requestId: string;
|
|
36
|
+
displayName: string;
|
|
37
|
+
sizePreset: string;
|
|
38
|
+
monthlySpendingLimitCents: number;
|
|
39
|
+
regionClass?: string;
|
|
40
|
+
storageGb?: number;
|
|
41
|
+
retentionClass?: string;
|
|
42
|
+
backupProfile?: string;
|
|
43
|
+
workspaceId?: string;
|
|
44
|
+
}): CreateDedicatedRuntimeInput;
|
|
45
|
+
export declare function createDedicatedRuntime(input: CreateDedicatedRuntimeInput): Promise<DedicatedRuntimeResponse>;
|
|
46
|
+
export declare function listDedicatedRuntimes(options?: {
|
|
47
|
+
limit?: number;
|
|
48
|
+
state?: string;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
leases?: HarnessLease[];
|
|
51
|
+
}>;
|
|
52
|
+
export declare function getDedicatedRuntime(leaseId: string): Promise<DedicatedRuntimeResponse>;
|
|
53
|
+
export declare function performDedicatedLeaseAction(leaseId: string, action: DedicatedLeaseAction): Promise<DedicatedRuntimeResponse>;
|
|
54
|
+
export declare function growDedicatedRuntime(leaseId: string, storageGb: number, options?: {
|
|
55
|
+
requestId?: string;
|
|
56
|
+
}): Promise<DedicatedRuntimeResponse>;
|
|
57
|
+
export declare function closeDedicatedRuntimeIncidentHold(leaseId: string, options?: {
|
|
58
|
+
requestId?: string;
|
|
59
|
+
}): Promise<DedicatedRuntimeResponse>;
|
|
60
|
+
export declare function destroyDedicatedRuntime(leaseId: string, options?: {
|
|
61
|
+
retentionIntent?: 'retain' | 'purge';
|
|
62
|
+
}): Promise<DedicatedRuntimeResponse>;
|
|
63
|
+
export declare function dedicatedLeaseId(lease: HarnessLease | undefined | null): string;
|
|
64
|
+
export declare function dedicatedLeaseState(lease: HarnessLease | undefined | null): string;
|
|
65
|
+
export declare function dedicatedLeaseDisplayName(lease: HarnessLease | undefined | null): string;
|
|
66
|
+
export declare function formatDedicatedRuntimeApiError(err: unknown): string | null;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEDICATED_LEASE_ACTIONS = void 0;
|
|
4
|
+
exports.dedicatedRuntimeCreateInput = dedicatedRuntimeCreateInput;
|
|
5
|
+
exports.createDedicatedRuntime = createDedicatedRuntime;
|
|
6
|
+
exports.listDedicatedRuntimes = listDedicatedRuntimes;
|
|
7
|
+
exports.getDedicatedRuntime = getDedicatedRuntime;
|
|
8
|
+
exports.performDedicatedLeaseAction = performDedicatedLeaseAction;
|
|
9
|
+
exports.growDedicatedRuntime = growDedicatedRuntime;
|
|
10
|
+
exports.closeDedicatedRuntimeIncidentHold = closeDedicatedRuntimeIncidentHold;
|
|
11
|
+
exports.destroyDedicatedRuntime = destroyDedicatedRuntime;
|
|
12
|
+
exports.dedicatedLeaseId = dedicatedLeaseId;
|
|
13
|
+
exports.dedicatedLeaseState = dedicatedLeaseState;
|
|
14
|
+
exports.dedicatedLeaseDisplayName = dedicatedLeaseDisplayName;
|
|
15
|
+
exports.formatDedicatedRuntimeApiError = formatDedicatedRuntimeApiError;
|
|
16
|
+
const api_fetch_1 = require("./api-fetch");
|
|
17
|
+
const harnesses_1 = require("./harnesses");
|
|
18
|
+
exports.DEDICATED_LEASE_ACTIONS = [
|
|
19
|
+
'start',
|
|
20
|
+
'stop',
|
|
21
|
+
'restart',
|
|
22
|
+
'snapshot',
|
|
23
|
+
'health-check',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Flags in, request body out — and the point of it being a function is the
|
|
27
|
+
* OMISSION.
|
|
28
|
+
*
|
|
29
|
+
* A field the user did not set must be ABSENT from the body, not present as
|
|
30
|
+
* `undefined`, because the server reaches its own default through
|
|
31
|
+
* `input.x || <default>`. Restating those defaults on the client looks
|
|
32
|
+
* harmless and is not: `--storage-gb` shipped with a client default of 100
|
|
33
|
+
* against a product default of 50, so a user who never chose a disk size was
|
|
34
|
+
* billed for twice the documented one and `workspaceDefaultGb` was unreachable
|
|
35
|
+
* through the CLI. The three neighbouring flags happened to agree, which only
|
|
36
|
+
* means the same bug was invisible in them.
|
|
37
|
+
*/
|
|
38
|
+
function dedicatedRuntimeCreateInput(input) {
|
|
39
|
+
return {
|
|
40
|
+
requestId: input.requestId,
|
|
41
|
+
displayName: input.displayName,
|
|
42
|
+
sizePreset: input.sizePreset,
|
|
43
|
+
monthlySpendingLimitCents: input.monthlySpendingLimitCents,
|
|
44
|
+
...(input.regionClass ? { regionClass: input.regionClass } : {}),
|
|
45
|
+
...(input.storageGb !== undefined ? { storageGb: input.storageGb } : {}),
|
|
46
|
+
...(input.retentionClass ? { retentionClass: input.retentionClass } : {}),
|
|
47
|
+
...(input.backupProfile
|
|
48
|
+
? { backupProfile: input.backupProfile }
|
|
49
|
+
: {}),
|
|
50
|
+
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function createDedicatedRuntime(input) {
|
|
54
|
+
const { requestId, workspaceId, ...body } = input;
|
|
55
|
+
if (workspaceId) {
|
|
56
|
+
return (0, api_fetch_1.apiFetch)(`/api/machines/dedicated?workspaceId=${encodeURIComponent(workspaceId)}`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
body,
|
|
59
|
+
headers: { 'Idempotency-Key': requestId },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return (0, api_fetch_1.apiFetch)('/api/machines/dedicated', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body,
|
|
65
|
+
headers: { 'Idempotency-Key': requestId },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function listDedicatedRuntimes(options = {}) {
|
|
69
|
+
return (0, harnesses_1.listHarnessLeases)({
|
|
70
|
+
...options,
|
|
71
|
+
machineProduct: 'dedicated-machine',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async function getDedicatedRuntime(leaseId) {
|
|
75
|
+
return (0, harnesses_1.getHarnessLease)(leaseId);
|
|
76
|
+
}
|
|
77
|
+
async function performDedicatedLeaseAction(leaseId, action) {
|
|
78
|
+
return (0, harnesses_1.performHarnessLeaseAction)(leaseId, action);
|
|
79
|
+
}
|
|
80
|
+
async function growDedicatedRuntime(leaseId, storageGb, options = {}) {
|
|
81
|
+
return (0, api_fetch_1.apiFetch)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/storage`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
body: { storageGb },
|
|
84
|
+
...(options.requestId ? { headers: { 'Idempotency-Key': options.requestId } } : {}),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function closeDedicatedRuntimeIncidentHold(leaseId, options = {}) {
|
|
88
|
+
return (0, api_fetch_1.apiFetch)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/incident-hold/close`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: {},
|
|
91
|
+
...(options.requestId ? { headers: { 'Idempotency-Key': options.requestId } } : {}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function destroyDedicatedRuntime(leaseId, options = {}) {
|
|
95
|
+
const query = options.retentionIntent
|
|
96
|
+
? `?retentionIntent=${encodeURIComponent(options.retentionIntent)}`
|
|
97
|
+
: '';
|
|
98
|
+
return (0, api_fetch_1.apiFetch)(`/api/machines/leases/${encodeURIComponent(leaseId)}${query}`, { method: 'DELETE' });
|
|
99
|
+
}
|
|
100
|
+
function dedicatedLeaseId(lease) {
|
|
101
|
+
return String(lease?.id ?? '');
|
|
102
|
+
}
|
|
103
|
+
function dedicatedLeaseState(lease) {
|
|
104
|
+
return String(lease?.state ?? 'unknown');
|
|
105
|
+
}
|
|
106
|
+
function dedicatedLeaseDisplayName(lease) {
|
|
107
|
+
return lease?.displayName ? String(lease.displayName) : '-';
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Dedicated Runtime commands are unusually likely to meet a server that has
|
|
111
|
+
* never heard of them.
|
|
112
|
+
*
|
|
113
|
+
* The CLI ships independently of the API (`@skrr-ai/cli` on npm), so "CLI newer
|
|
114
|
+
* than the deployed server" is the normal state for anyone who runs
|
|
115
|
+
* `npm update`, and this feature widened that window: twelve commands arrived at
|
|
116
|
+
* once, and three of them call endpoints that land in a later deploy. The server
|
|
117
|
+
* redacts an unknown-route 404 to "An unexpected error occurred.", so every one
|
|
118
|
+
* of those users got eight words that cannot be told apart from a wrong lease
|
|
119
|
+
* id or a broken server.
|
|
120
|
+
*
|
|
121
|
+
* A 404 carrying no product `code` is that case. Naming it is the difference
|
|
122
|
+
* between "wait for the deploy" and "open a bug".
|
|
123
|
+
*/
|
|
124
|
+
function describeMissingEndpoint(err) {
|
|
125
|
+
if (err.status !== 404) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(err.body || '');
|
|
130
|
+
// A product code means the server DID handle the route and refused on the
|
|
131
|
+
// merits — that message is the useful one and must not be overwritten.
|
|
132
|
+
if (parsed.code) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Unparseable body: no product code by definition.
|
|
138
|
+
}
|
|
139
|
+
return ('This server does not have that Dedicated Runtime endpoint. Your CLI is probably ahead of ' +
|
|
140
|
+
'the deployment — `skrr context` names the server it is talking to, and `skrr machines ' +
|
|
141
|
+
'dedicated list` will still work if the feature is deployed at all.');
|
|
142
|
+
}
|
|
143
|
+
function formatDedicatedRuntimeApiError(err) {
|
|
144
|
+
if (!(err instanceof api_fetch_1.ApiFetchError) || !err.body) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (err.status === 401) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const missingEndpoint = describeMissingEndpoint(err);
|
|
151
|
+
if (missingEndpoint) {
|
|
152
|
+
return missingEndpoint;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(err.body);
|
|
156
|
+
return parsed.message || parsed.error || parsed.code || null;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return err.body.trim() || null;
|
|
160
|
+
}
|
|
161
|
+
}
|
package/dist/lib/harnesses.d.ts
CHANGED
|
@@ -97,6 +97,11 @@ export interface HarnessLease {
|
|
|
97
97
|
assignable?: boolean;
|
|
98
98
|
reasons?: string[];
|
|
99
99
|
} & Record<string, unknown>;
|
|
100
|
+
storage?: {
|
|
101
|
+
retentionClass?: string;
|
|
102
|
+
lastBackupAt?: string;
|
|
103
|
+
backupProfile?: string;
|
|
104
|
+
};
|
|
100
105
|
createdAt?: string;
|
|
101
106
|
updatedAt?: string;
|
|
102
107
|
}
|
package/dist/lib/harnesses.js
CHANGED
|
@@ -177,8 +177,11 @@ async function listHarnessLeases(options = {}) {
|
|
|
177
177
|
query.set('state', options.state);
|
|
178
178
|
if (options.machineProduct)
|
|
179
179
|
query.set('machineProduct', options.machineProduct);
|
|
180
|
-
const
|
|
181
|
-
|
|
180
|
+
const encoded = query.toString();
|
|
181
|
+
if (encoded) {
|
|
182
|
+
return (0, api_fetch_1.apiFetch)(`/api/machines/leases?${encoded}`);
|
|
183
|
+
}
|
|
184
|
+
return (0, api_fetch_1.apiFetch)('/api/machines/leases');
|
|
182
185
|
}
|
|
183
186
|
async function getHarnessLease(leaseId) {
|
|
184
187
|
return (0, api_fetch_1.apiFetch)(`/api/machines/leases/${encodeURIComponent(leaseId)}`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A process running one autonomous ACTION binds every write to it, so the same
|
|
3
|
+
* action re-run in a new process converges on the server instead of writing
|
|
4
|
+
* twice. The rule itself lives in `@skrr-ai/data-provider` (`idempotency.ts`),
|
|
5
|
+
* shared with the web client; the CLI only says which action it is.
|
|
6
|
+
*
|
|
7
|
+
* Bound by both transports — the `dataService` adapter and `apiFetch` — because
|
|
8
|
+
* either may be the first to write.
|
|
9
|
+
*/
|
|
10
|
+
export declare function bindAutonomousIdempotencyScope(env?: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bindAutonomousIdempotencyScope = bindAutonomousIdempotencyScope;
|
|
4
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
|
+
/**
|
|
6
|
+
* A process running one autonomous ACTION binds every write to it, so the same
|
|
7
|
+
* action re-run in a new process converges on the server instead of writing
|
|
8
|
+
* twice. The rule itself lives in `@skrr-ai/data-provider` (`idempotency.ts`),
|
|
9
|
+
* shared with the web client; the CLI only says which action it is.
|
|
10
|
+
*
|
|
11
|
+
* Bound by both transports — the `dataService` adapter and `apiFetch` — because
|
|
12
|
+
* either may be the first to write.
|
|
13
|
+
*/
|
|
14
|
+
function bindAutonomousIdempotencyScope(env = process.env) {
|
|
15
|
+
(0, data_provider_1.setIdempotencyScope)(env.OVERSKY_AUTONOMOUS_ACTION_KEY?.trim() || null);
|
|
16
|
+
}
|
package/dist/lib/node-adapter.js
CHANGED
|
@@ -5,7 +5,8 @@ exports.clearAdapterCredentialOverride = clearAdapterCredentialOverride;
|
|
|
5
5
|
exports.getAdapterCredentialOverride = getAdapterCredentialOverride;
|
|
6
6
|
exports.createNodeAdapter = createNodeAdapter;
|
|
7
7
|
exports.__recomputeCredentialForTest = __recomputeCredentialForTest;
|
|
8
|
-
const
|
|
8
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
9
|
+
const idempotency_scope_1 = require("./idempotency-scope");
|
|
9
10
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
10
11
|
const config_1 = require("./config");
|
|
11
12
|
const auth_storage_1 = require("./auth-storage");
|
|
@@ -72,6 +73,7 @@ function currentCredential() {
|
|
|
72
73
|
}
|
|
73
74
|
function createNodeAdapter(opts = {}) {
|
|
74
75
|
const { onTokenRefresh, onAuthFailure } = opts;
|
|
76
|
+
(0, idempotency_scope_1.bindAutonomousIdempotencyScope)();
|
|
75
77
|
// In-process single-flight: collapses N parallel 401s into one refresh
|
|
76
78
|
// round-trip. Cross-process serialization happens inside refresh.ts.
|
|
77
79
|
let refreshInFlight = null;
|
|
@@ -157,15 +159,6 @@ function createNodeAdapter(opts = {}) {
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
const finalUrl = appendQuery(resolveUrl(url), options?.params);
|
|
160
|
-
const actionKey = process.env.OVERSKY_AUTONOMOUS_ACTION_KEY?.trim();
|
|
161
|
-
const hasIdempotencyKey = Object.keys(headers).some((key) => key.toLowerCase() === 'x-idempotency-key');
|
|
162
|
-
if (actionKey && method !== 'GET' && !isFormData && !hasIdempotencyKey) {
|
|
163
|
-
const requestUrl = new URL(finalUrl);
|
|
164
|
-
const scope = `${actionKey}\u001f${method}\u001f${requestUrl.pathname}${requestUrl.search}\u001f${typeof finalBody === 'string' ? finalBody : ''}`;
|
|
165
|
-
headers['X-Idempotency-Key'] = `autonomous:${(0, node_crypto_1.createHash)('sha256')
|
|
166
|
-
.update(scope)
|
|
167
|
-
.digest('hex')}`;
|
|
168
|
-
}
|
|
169
162
|
return fetch(finalUrl, { method, headers, body: finalBody });
|
|
170
163
|
}
|
|
171
164
|
/**
|
|
@@ -291,6 +284,15 @@ function createNodeAdapter(opts = {}) {
|
|
|
291
284
|
return true;
|
|
292
285
|
}
|
|
293
286
|
async function request(method, url, body, options, isFormData = false) {
|
|
287
|
+
// Writes that arrive through `request.ts` already carry a key. A direct
|
|
288
|
+
// adapter call gets one by the same shared rule, chosen HERE, once, so the
|
|
289
|
+
// 401-refresh retries below resend the same key rather than a new one.
|
|
290
|
+
if (method !== 'GET' && !isFormData && !(0, data_provider_1.hasIdempotencyKey)(options?.headers)) {
|
|
291
|
+
options = {
|
|
292
|
+
...(options ?? {}),
|
|
293
|
+
headers: (0, data_provider_1.withIdempotencyKey)(options?.headers, { method, url, body }),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
294
296
|
let res = await rawRequest(method, url, body, options, isFormData);
|
|
295
297
|
// Never retry auth endpoints themselves — prevents refresh recursion
|
|
296
298
|
// and lets login/logout/refresh surface their real errors.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tell a reader when an empty canonical view means "nothing happened" and when
|
|
3
|
+
* it means "this Task predates the records this view reads".
|
|
4
|
+
*
|
|
5
|
+
* Canonical Task Model v1 shipped with an explicit decision not to migrate
|
|
6
|
+
* legacy data, so a Task created before 2026-09-11 has no TaskUpdate, TaskResult
|
|
7
|
+
* or TaskActivityEvent rows — its history is in `activityLog`, `TaskWorkEvent`
|
|
8
|
+
* and TASK_OUTPUT comments. Every canonical read surface therefore answered
|
|
9
|
+
* "No activity." / "Result: none submitted" / nothing at all for Tasks with
|
|
10
|
+
* years of history, stating absence as fact.
|
|
11
|
+
*
|
|
12
|
+
* The marker is that `specVersion` is ABSENT — a field the caller can only
|
|
13
|
+
* notice by its own missingness, which is why this belongs in one helper rather
|
|
14
|
+
* than in each command.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isLegacyTask(taskId: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* The sentence to print instead of claiming emptiness. `surface` names what the
|
|
19
|
+
* reader just asked for, so the message says which view is empty rather than
|
|
20
|
+
* implying the Task is.
|
|
21
|
+
*/
|
|
22
|
+
export declare function legacyEmptyStateMessage(surface: string): string;
|
|
23
|
+
/** Render the right empty state for a canonical view that came back empty. */
|
|
24
|
+
export declare function emptyCanonicalView(taskId: string, surface: string): Promise<string>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLegacyTask = isLegacyTask;
|
|
4
|
+
exports.legacyEmptyStateMessage = legacyEmptyStateMessage;
|
|
5
|
+
exports.emptyCanonicalView = emptyCanonicalView;
|
|
6
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
7
|
+
/**
|
|
8
|
+
* Tell a reader when an empty canonical view means "nothing happened" and when
|
|
9
|
+
* it means "this Task predates the records this view reads".
|
|
10
|
+
*
|
|
11
|
+
* Canonical Task Model v1 shipped with an explicit decision not to migrate
|
|
12
|
+
* legacy data, so a Task created before 2026-09-11 has no TaskUpdate, TaskResult
|
|
13
|
+
* or TaskActivityEvent rows — its history is in `activityLog`, `TaskWorkEvent`
|
|
14
|
+
* and TASK_OUTPUT comments. Every canonical read surface therefore answered
|
|
15
|
+
* "No activity." / "Result: none submitted" / nothing at all for Tasks with
|
|
16
|
+
* years of history, stating absence as fact.
|
|
17
|
+
*
|
|
18
|
+
* The marker is that `specVersion` is ABSENT — a field the caller can only
|
|
19
|
+
* notice by its own missingness, which is why this belongs in one helper rather
|
|
20
|
+
* than in each command.
|
|
21
|
+
*/
|
|
22
|
+
async function isLegacyTask(taskId) {
|
|
23
|
+
try {
|
|
24
|
+
const task = (await data_provider_1.dataService.getTask(taskId));
|
|
25
|
+
return task !== undefined && task !== null && task.specVersion === undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Never turn a diagnostic into a failure: the caller is rendering an empty
|
|
29
|
+
// state, not making a decision.
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The sentence to print instead of claiming emptiness. `surface` names what the
|
|
35
|
+
* reader just asked for, so the message says which view is empty rather than
|
|
36
|
+
* implying the Task is.
|
|
37
|
+
*/
|
|
38
|
+
function legacyEmptyStateMessage(surface) {
|
|
39
|
+
return (`No ${surface} recorded under the canonical Task model.\n` +
|
|
40
|
+
'This Task predates it, so its history was never migrated — read it with ' +
|
|
41
|
+
'`skrr tasks show`, `skrr tasks comments list`, or `skrr tasks transcript`.');
|
|
42
|
+
}
|
|
43
|
+
/** Render the right empty state for a canonical view that came back empty. */
|
|
44
|
+
async function emptyCanonicalView(taskId, surface) {
|
|
45
|
+
return (await isLegacyTask(taskId))
|
|
46
|
+
? legacyEmptyStateMessage(surface)
|
|
47
|
+
: `No ${surface} recorded yet.`;
|
|
48
|
+
}
|
package/dist/lib/tasks.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export type TaskCreateFlagValues = {
|
|
|
59
59
|
description?: string;
|
|
60
60
|
presetKey?: string;
|
|
61
61
|
}>;
|
|
62
|
+
'review-policy'?: string;
|
|
62
63
|
'start-date'?: string;
|
|
63
64
|
'due-date'?: string;
|
|
64
65
|
estimate?: number;
|
|
@@ -98,6 +99,37 @@ export declare function parseExpectations(specs: string[] | undefined): {
|
|
|
98
99
|
error: string;
|
|
99
100
|
};
|
|
100
101
|
export declare const EXPECT_FLAG_HELP: string;
|
|
102
|
+
/**
|
|
103
|
+
* What `--default-expect` means, as a value rather than a side effect.
|
|
104
|
+
*
|
|
105
|
+
* `clear` REMOVES the Space default, and it has to exist. Every Expectation gates
|
|
106
|
+
* closure and a Space default is inherited by every task created afterwards, so a
|
|
107
|
+
* default set by mistake — or one that outlived its project — gated the whole
|
|
108
|
+
* board with no way back. `tasks update --expect` already had this sentinel; the
|
|
109
|
+
* Space flag, the one the design calls "configured once and then holds", did not.
|
|
110
|
+
* The only exit was to waive the expectation on every task, which writes `dropped`
|
|
111
|
+
* into the ledger about a requirement that was never real. That is a false record,
|
|
112
|
+
* not a workaround.
|
|
113
|
+
*
|
|
114
|
+
* `null` rather than `[]`: the route declares the field `.nullable()` and the API
|
|
115
|
+
* turns null into an `$unset`, so the Space stops carrying a default instead of
|
|
116
|
+
* carrying an empty one. `undefined` means the flag was not passed and the
|
|
117
|
+
* existing default must be left exactly as it is.
|
|
118
|
+
*
|
|
119
|
+
* `clear` must be the SOLE value. Combined with an expectation it is two
|
|
120
|
+
* contradictory instructions, and honouring one of them silently is how a caller
|
|
121
|
+
* ends up with a contract they did not ask for.
|
|
122
|
+
*
|
|
123
|
+
* Extracted so the three outcomes can be tested without a command harness — the
|
|
124
|
+
* bug being fixed was in this decision, not in the HTTP call after it.
|
|
125
|
+
*/
|
|
126
|
+
export declare function resolveDefaultExpectations(values: string[] | undefined): {
|
|
127
|
+
ok: true;
|
|
128
|
+
value: Array<Record<string, unknown>> | null | undefined;
|
|
129
|
+
} | {
|
|
130
|
+
ok: false;
|
|
131
|
+
error: string;
|
|
132
|
+
};
|
|
101
133
|
/**
|
|
102
134
|
* Parse a deliverable coordinate. Also delegated — `:` and `@` and now `/` all
|
|
103
135
|
* mean different things, and a second implementation of that grammar is how the
|
package/dist/lib/tasks.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.taskUpdateMutationGuard = taskUpdateMutationGuard;
|
|
|
6
6
|
exports.executionIsolationFromWorktreeFlag = executionIsolationFromWorktreeFlag;
|
|
7
7
|
exports.parseExpectation = parseExpectation;
|
|
8
8
|
exports.parseExpectations = parseExpectations;
|
|
9
|
+
exports.resolveDefaultExpectations = resolveDefaultExpectations;
|
|
9
10
|
exports.parseDeliverableCoordinate = parseDeliverableCoordinate;
|
|
10
11
|
exports.buildTaskCreatePayload = buildTaskCreatePayload;
|
|
11
12
|
exports.boundSessionAgentId = boundSessionAgentId;
|
|
@@ -156,10 +157,54 @@ const kindsForHelp = Array.isArray(data_provider_1.TASK_OUTCOME_KINDS)
|
|
|
156
157
|
const presetsForHelp = Array.isArray(data_provider_1.EXPECTATION_PRESETS)
|
|
157
158
|
? data_provider_1.EXPECTATION_PRESETS.map((preset) => `@${preset.key}`).join(', ')
|
|
158
159
|
: '@merged-to-main, @pdf-delivered';
|
|
159
|
-
exports.EXPECT_FLAG_HELP = 'What this task must produce
|
|
160
|
+
exports.EXPECT_FLAG_HELP = 'What this task must produce. Each one GATES closure: the Task cannot enter a ' +
|
|
161
|
+
'completed state until every Expectation is assessed met or waived ' +
|
|
162
|
+
'(`tasks expectations assess`). Form: <kind>[/<format>][:<target>] or @<preset> — ' +
|
|
160
163
|
`e.g. 'code:origin/main', 'file/pdf:Drive/Reports', '@merged-to-main'. ` +
|
|
161
164
|
`Kinds: ${kindsForHelp}. Presets: ${presetsForHelp}. ` +
|
|
162
165
|
'Anything else is stored as a plain-language expectation a person settles. Repeatable.';
|
|
166
|
+
/**
|
|
167
|
+
* What `--default-expect` means, as a value rather than a side effect.
|
|
168
|
+
*
|
|
169
|
+
* `clear` REMOVES the Space default, and it has to exist. Every Expectation gates
|
|
170
|
+
* closure and a Space default is inherited by every task created afterwards, so a
|
|
171
|
+
* default set by mistake — or one that outlived its project — gated the whole
|
|
172
|
+
* board with no way back. `tasks update --expect` already had this sentinel; the
|
|
173
|
+
* Space flag, the one the design calls "configured once and then holds", did not.
|
|
174
|
+
* The only exit was to waive the expectation on every task, which writes `dropped`
|
|
175
|
+
* into the ledger about a requirement that was never real. That is a false record,
|
|
176
|
+
* not a workaround.
|
|
177
|
+
*
|
|
178
|
+
* `null` rather than `[]`: the route declares the field `.nullable()` and the API
|
|
179
|
+
* turns null into an `$unset`, so the Space stops carrying a default instead of
|
|
180
|
+
* carrying an empty one. `undefined` means the flag was not passed and the
|
|
181
|
+
* existing default must be left exactly as it is.
|
|
182
|
+
*
|
|
183
|
+
* `clear` must be the SOLE value. Combined with an expectation it is two
|
|
184
|
+
* contradictory instructions, and honouring one of them silently is how a caller
|
|
185
|
+
* ends up with a contract they did not ask for.
|
|
186
|
+
*
|
|
187
|
+
* Extracted so the three outcomes can be tested without a command harness — the
|
|
188
|
+
* bug being fixed was in this decision, not in the HTTP call after it.
|
|
189
|
+
*/
|
|
190
|
+
function resolveDefaultExpectations(values) {
|
|
191
|
+
if (values === undefined || values.length === 0)
|
|
192
|
+
return { ok: true, value: undefined };
|
|
193
|
+
const clears = values.filter((value) => value.trim().toLowerCase() === 'clear');
|
|
194
|
+
if (clears.length > 0 && values.length > 1) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
error: '--default-expect clear removes the space default and cannot be combined with an ' +
|
|
198
|
+
'expectation. Run it alone, then set the new default.',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (clears.length > 0)
|
|
202
|
+
return { ok: true, value: null };
|
|
203
|
+
const parsed = parseExpectations(values);
|
|
204
|
+
if (!parsed.ok)
|
|
205
|
+
return { ok: false, error: parsed.error };
|
|
206
|
+
return { ok: true, value: parsed.value };
|
|
207
|
+
}
|
|
163
208
|
/**
|
|
164
209
|
* Parse a deliverable coordinate. Also delegated — `:` and `@` and now `/` all
|
|
165
210
|
* mean different things, and a second implementation of that grammar is how the
|
|
@@ -214,6 +259,7 @@ function buildTaskCreatePayload(body, flags = {}) {
|
|
|
214
259
|
assigneeUserIds: flags['assignee-user'],
|
|
215
260
|
executionMode: flags['execution-mode'],
|
|
216
261
|
expectations: flags.expectations,
|
|
262
|
+
reviewPolicy: flags['review-policy'],
|
|
217
263
|
startDate: flags['start-date'],
|
|
218
264
|
dueDate: flags['due-date'],
|
|
219
265
|
estimate: flags.estimate,
|
package/dist/lib/triggers.js
CHANGED
|
@@ -147,6 +147,13 @@ exports.TRIGGER_EVENT_CATALOG = {
|
|
|
147
147
|
'task.assigned',
|
|
148
148
|
'task.completed',
|
|
149
149
|
'task.archived',
|
|
150
|
+
'task.unassigned',
|
|
151
|
+
'task.scheduled',
|
|
152
|
+
'task.rescheduled',
|
|
153
|
+
'task.unscheduled',
|
|
154
|
+
'task.deleted',
|
|
155
|
+
'task.execution_completed',
|
|
156
|
+
'task.execution_failed',
|
|
150
157
|
],
|
|
151
158
|
space: [
|
|
152
159
|
'space.created',
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Sync's own telemetry, readable.
|
|
3
|
+
*
|
|
4
|
+
* Work Sync v1 is ON at 100% (owner decision, 2026-09-08) with two of its exit
|
|
5
|
+
* criteria knowingly overridden rather than satisfied: `shadow_mismatch` and
|
|
6
|
+
* `resume_error` rows from a one-user sample were unexplained, and
|
|
7
|
+
* revocation-to-purge latency had never been measured. Root `CLAUDE.md` records
|
|
8
|
+
* that an unexplained board divergence is therefore "a live hypothesis, not an
|
|
9
|
+
* impossibility, and is the first thing to suspect".
|
|
10
|
+
*
|
|
11
|
+
* The server has computed the verdict all along — `GET
|
|
12
|
+
* /api/work-sync/spaces/:spaceId/telemetry-summary` returns the per-event counts,
|
|
13
|
+
* the repair backlog, and a `ready`/`hold`/`insufficient_evidence` decision with
|
|
14
|
+
* its reasons. Nothing outside the API could read it, so checking the criteria
|
|
15
|
+
* meant a query against the production database. An exit criterion that can only
|
|
16
|
+
* be read by hand is one that does not get read.
|
|
17
|
+
*
|
|
18
|
+
* A domain lib rather than an inline `/api/...` in the command, per the repo's
|
|
19
|
+
* CLI rule: a command reaches the API through its domain lib or `dataService`,
|
|
20
|
+
* never by composing the URL at the call site.
|
|
21
|
+
*/
|
|
22
|
+
export type WorkSyncEventStats = {
|
|
23
|
+
count: number;
|
|
24
|
+
p50DurationMs: number | null;
|
|
25
|
+
p95DurationMs: number | null;
|
|
26
|
+
p50Bytes: number | null;
|
|
27
|
+
p95Bytes: number | null;
|
|
28
|
+
mismatchCount: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* What happened to WRITES of one operation kind, from the durable command queue.
|
|
32
|
+
* Rates are `null` when their denominator is zero: conflict rates are over
|
|
33
|
+
* settled commands (accepted + dead-lettered), the duplicate rate over deliveries.
|
|
34
|
+
*/
|
|
35
|
+
export type WorkSyncCommandStats = {
|
|
36
|
+
commands: number;
|
|
37
|
+
deliveries: number;
|
|
38
|
+
duplicateDeliveries: number;
|
|
39
|
+
accepted: number;
|
|
40
|
+
pending: number;
|
|
41
|
+
deadLettered: number;
|
|
42
|
+
versionConflicts: number;
|
|
43
|
+
authorizationChanged: number;
|
|
44
|
+
expired: number;
|
|
45
|
+
retryExhausted: number;
|
|
46
|
+
otherRejections: number;
|
|
47
|
+
rates: {
|
|
48
|
+
versionConflict: number | null;
|
|
49
|
+
authorizationChanged: number | null;
|
|
50
|
+
duplicateDelivery: number | null;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export type WorkSyncTelemetrySummary = {
|
|
54
|
+
sampledEvents: number;
|
|
55
|
+
events: Record<string, WorkSyncEventStats>;
|
|
56
|
+
/** Absent from a server older than the write-side rates. */
|
|
57
|
+
commands?: Record<string, WorkSyncCommandStats>;
|
|
58
|
+
/**
|
|
59
|
+
* Cold opens of a task detail plane that found (hit) or did not find (miss) a
|
|
60
|
+
* completed coverage record on the device. `hitRate` is null with no cold
|
|
61
|
+
* opens. Absent from a server older than the coverage read path.
|
|
62
|
+
*/
|
|
63
|
+
coverage?: {
|
|
64
|
+
hits: number;
|
|
65
|
+
misses: number;
|
|
66
|
+
hitRate: number | null;
|
|
67
|
+
};
|
|
68
|
+
repairBacklog: number;
|
|
69
|
+
rollout: {
|
|
70
|
+
verdict: 'ready' | 'hold' | 'insufficient_evidence';
|
|
71
|
+
reasons: string[];
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/** What each `rollout.reasons` code means, in the reader's terms. */
|
|
75
|
+
export declare const WORK_SYNC_HOLD_REASONS: Record<string, string>;
|
|
76
|
+
export declare function fetchWorkSyncTelemetrySummary(spaceId: string, { limit }?: {
|
|
77
|
+
limit?: number;
|
|
78
|
+
}): Promise<WorkSyncTelemetrySummary>;
|
|
79
|
+
/**
|
|
80
|
+
* What `POST /reconcile` actually returns, field for field.
|
|
81
|
+
*
|
|
82
|
+
* This was typed `{repaired?: number; remaining?: number}` — two names the server
|
|
83
|
+
* has never sent. A wrong optional type is worse than none: every field reads as
|
|
84
|
+
* `undefined` at runtime and TypeScript agrees it might be, so a command printing
|
|
85
|
+
* `repaired` would have compiled, run, and reported nothing wrong.
|
|
86
|
+
*
|
|
87
|
+
* `upserts` is the one that answers the question a reader is asking: how many
|
|
88
|
+
* projection entries were found STALE against authoritative task truth. It is a
|
|
89
|
+
* divergence count, which is why this command exists — it is the same question the
|
|
90
|
+
* browser's shadow comparison answers, asked server-side and without a browser.
|
|
91
|
+
*/
|
|
92
|
+
export type WorkSyncReconciliation = {
|
|
93
|
+
/** Projection entries found stale against task truth and republished. */
|
|
94
|
+
upserts: number;
|
|
95
|
+
/** TaskCards published for tasks that no longer exist. */
|
|
96
|
+
removes: number;
|
|
97
|
+
summaryUpserts: number;
|
|
98
|
+
summaryRemoves: number;
|
|
99
|
+
/** Pending repair rows queued by earlier failures, before this run settled them. */
|
|
100
|
+
backlog: number;
|
|
101
|
+
/** A scope larger than the 1000-row read cap: the verdict covers only a prefix. */
|
|
102
|
+
truncated: boolean;
|
|
103
|
+
success?: boolean;
|
|
104
|
+
};
|
|
105
|
+
export declare function reconcileWorkSyncScope(spaceId: string): Promise<WorkSyncReconciliation>;
|