@skrr-ai/cli 0.1.41 → 0.1.43
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/commands/balance/overage.js +4 -3
- package/dist/commands/balance/plan.js +10 -6
- package/dist/commands/balance/usage/events.js +3 -2
- package/dist/commands/balance/usage.js +2 -1
- package/dist/commands/browser/install.js +3 -2
- package/dist/commands/browser/uninstall.js +3 -2
- package/dist/commands/code/install.js +6 -6
- package/dist/commands/followups/remind.js +4 -0
- package/dist/commands/harnesses/install.d.ts +29 -0
- package/dist/commands/harnesses/install.js +153 -25
- package/dist/commands/harnesses/installers.d.ts +10 -0
- package/dist/commands/harnesses/installers.js +31 -0
- package/dist/commands/harnesses/list.js +3 -2
- package/dist/commands/skills/import-as-actions.js +6 -15
- package/dist/commands/tasks/attachments/set-role.d.ts +24 -0
- package/dist/commands/tasks/attachments/set-role.js +60 -0
- package/dist/commands/tasks/attachments/upload.d.ts +1 -0
- package/dist/commands/tasks/attachments/upload.js +18 -52
- package/dist/commands/tasks/comments/add.d.ts +1 -0
- package/dist/commands/tasks/comments/add.js +67 -2
- package/dist/commands/tasks/complete.d.ts +1 -0
- package/dist/commands/tasks/complete.js +69 -1
- package/dist/commands/tasks/result/submit.d.ts +3 -0
- package/dist/commands/tasks/result/submit.js +78 -5
- package/dist/commands/tasks/update.js +4 -2
- package/dist/commands/tasks/updates/add.d.ts +1 -0
- package/dist/commands/tasks/updates/add.js +63 -0
- package/dist/lib/api-fetch.js +7 -2
- package/dist/lib/cli-installers.d.ts +63 -4
- package/dist/lib/cli-installers.js +98 -8
- package/dist/lib/dedicated-machines.js +4 -2
- package/dist/lib/file-mime.js +1 -1
- package/dist/lib/first-party-harness-agent.js +6 -5
- package/dist/lib/first-party-harness-doctor.js +15 -32
- package/dist/lib/first-party-harness-managed.d.ts +9 -4
- package/dist/lib/first-party-harness-managed.js +13 -10
- package/dist/lib/first-party-harness.d.ts +25 -21
- package/dist/lib/first-party-harness.js +40 -27
- package/dist/lib/followups.d.ts +2 -0
- package/dist/lib/harness-provider-input.d.ts +18 -12
- package/dist/lib/harness-provider-input.js +18 -12
- package/dist/lib/harness-tiers.d.ts +4 -3
- package/dist/lib/harness-tiers.js +4 -3
- package/dist/lib/html-text.js +25 -0
- package/dist/lib/local-skills.d.ts +27 -0
- package/dist/lib/local-skills.js +38 -0
- package/dist/lib/session-task-endpoints.d.ts +1 -1
- package/dist/lib/session-task-endpoints.js +3 -0
- package/dist/lib/task-asset-upload.d.ts +84 -0
- package/dist/lib/task-asset-upload.js +374 -0
- package/dist/lib/task-closure.d.ts +28 -0
- package/dist/lib/task-closure.js +37 -0
- package/dist/lib/tasks.d.ts +8 -0
- package/dist/lib/tasks.js +16 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +113 -39
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +148 -71
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +21 -33
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +24 -50
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +19 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +26 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +9 -5
- 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 +1 -13
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +113 -39
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +146 -70
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +21 -33
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +24 -49
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +19 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +27 -11
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +9 -5
- 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 -4
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4025 -3938
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +12853 -12721
- package/package.json +2 -2
package/dist/lib/api-fetch.js
CHANGED
|
@@ -93,9 +93,14 @@ async function apiFetch(pathOrUrl, opts = {}) {
|
|
|
93
93
|
? assertTrustedAutonomousUrl(pathOrUrl, baseURL)
|
|
94
94
|
: pathOrUrl
|
|
95
95
|
: `${baseURL.replace(/\/+$/, '')}${pathOrUrl.startsWith('/') ? '' : '/'}${pathOrUrl}`;
|
|
96
|
+
// A FormData body passes through untouched — fetch sets the multipart
|
|
97
|
+
// boundary itself, and stringifying would destroy it. Upload commands
|
|
98
|
+
// (tasks attachments) need this on the session-scoped path, which is not
|
|
99
|
+
// in dataService.
|
|
100
|
+
const isFormData = typeof FormData !== 'undefined' && opts.body instanceof FormData;
|
|
96
101
|
let body;
|
|
97
102
|
if (opts.body !== undefined && method !== 'GET') {
|
|
98
|
-
body = JSON.stringify(opts.body);
|
|
103
|
+
body = isFormData ? opts.body : JSON.stringify(opts.body);
|
|
99
104
|
}
|
|
100
105
|
// Same rule as every `dataService` write (`@skrr-ai/data-provider`
|
|
101
106
|
// idempotency), chosen once so the 401 retries below resend the same key.
|
|
@@ -145,7 +150,7 @@ async function apiFetch(pathOrUrl, opts = {}) {
|
|
|
145
150
|
Accept: 'application/json',
|
|
146
151
|
'X-Oversky-Origin': (0, refresh_1.getCliOrigin)(),
|
|
147
152
|
};
|
|
148
|
-
if (opts.body !== undefined && method !== 'GET') {
|
|
153
|
+
if (opts.body !== undefined && method !== 'GET' && !isFormData) {
|
|
149
154
|
headers['Content-Type'] = 'application/json';
|
|
150
155
|
}
|
|
151
156
|
if (credential.token) {
|
|
@@ -25,12 +25,16 @@ export interface ManagedCliProviderState {
|
|
|
25
25
|
source?: string;
|
|
26
26
|
installable?: boolean;
|
|
27
27
|
installDisabledReason?: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the daemon can update this installed CLI in place, and why not.
|
|
30
|
+
* ABSENT from a daemon older than managed updates (OSK-10096) — read that as
|
|
31
|
+
* "this daemon cannot say", never as `false`.
|
|
32
|
+
*/
|
|
33
|
+
updatable?: boolean;
|
|
34
|
+
updateDisabledReason?: string | null;
|
|
28
35
|
platformSupported?: boolean;
|
|
29
36
|
authentication?: ManagedCliAuthentication | null;
|
|
30
|
-
job?:
|
|
31
|
-
id?: string;
|
|
32
|
-
status?: string;
|
|
33
|
-
} | null;
|
|
37
|
+
job?: ManagedCliInstallJob | null;
|
|
34
38
|
}
|
|
35
39
|
export interface CliInstallersResponse {
|
|
36
40
|
daemonId?: string;
|
|
@@ -143,18 +147,34 @@ export declare function readinessForRows(rows: readonly ReadinessRow[], credenti
|
|
|
143
147
|
}>;
|
|
144
148
|
export interface ManagedCliInstallJob {
|
|
145
149
|
id: string;
|
|
150
|
+
provider?: string;
|
|
151
|
+
displayName?: string;
|
|
146
152
|
status: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
153
|
+
/** `update` for an in-place update; absent from an older daemon, meaning install. */
|
|
154
|
+
kind?: 'install' | 'update';
|
|
155
|
+
startedAt?: string;
|
|
156
|
+
previousVersion?: string | null;
|
|
157
|
+
installedVersion?: string | null;
|
|
147
158
|
error?: string;
|
|
148
159
|
/**
|
|
149
160
|
* The daemon's job log: the LAST lines only (a sliding window, 200 lines in
|
|
150
161
|
* `daemon/src/cli-installers.ts`). This is the field the daemon sends.
|
|
151
162
|
*/
|
|
152
163
|
logTail?: string[];
|
|
164
|
+
/**
|
|
165
|
+
* Lines the job has EVER logged (daemons with OSK-10179). With it, the lines
|
|
166
|
+
* new since the last poll are exactly the last `count − printed` of the tail,
|
|
167
|
+
* even when they repeat.
|
|
168
|
+
*/
|
|
169
|
+
logLineCount?: number;
|
|
153
170
|
/** Never sent by any daemon; kept so an older caller's shape still types. */
|
|
154
171
|
log?: string[];
|
|
155
172
|
}
|
|
156
173
|
export interface StartInstallResponse {
|
|
157
174
|
started?: boolean;
|
|
175
|
+
/** The daemon declined because the CLI is already there. */
|
|
176
|
+
alreadyInstalled?: boolean;
|
|
177
|
+
alreadyRunning?: boolean;
|
|
158
178
|
provider?: ManagedCliProviderState;
|
|
159
179
|
job?: {
|
|
160
180
|
id?: string;
|
|
@@ -162,6 +182,35 @@ export interface StartInstallResponse {
|
|
|
162
182
|
};
|
|
163
183
|
}
|
|
164
184
|
export declare function startManagedCliInstall(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
|
|
185
|
+
/**
|
|
186
|
+
* How long after a start request times out a job for the same provider is taken
|
|
187
|
+
* to be the one that request began.
|
|
188
|
+
*
|
|
189
|
+
* The relay holds the request while the daemon answers, and a gateway can cut
|
|
190
|
+
* that off at its own deadline: an update returned HTTP 504 after 21.9s while
|
|
191
|
+
* the job it started ran to success, and the caller was told the outcome was
|
|
192
|
+
* unknown (OSK-10096 verifier). A job that started within this window of the
|
|
193
|
+
* request is that request's job.
|
|
194
|
+
*/
|
|
195
|
+
export declare const START_TIMEOUT_JOB_WINDOW_MS = 120000;
|
|
196
|
+
/** Ask the daemon to update an installed CLI in place (OSK-10096). */
|
|
197
|
+
export declare function startManagedCliUpdate(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
|
|
198
|
+
/**
|
|
199
|
+
* How an update job ended, from the versions the daemon recorded on it:
|
|
200
|
+
* "Updated Claude Code 2.1.270 → 2.1.280." or "Claude Code is still 2.1.270:
|
|
201
|
+
* its updater found nothing newer." Null when the job does not carry both.
|
|
202
|
+
*/
|
|
203
|
+
/**
|
|
204
|
+
* The job a timed-out start request began, or null when none can be claimed.
|
|
205
|
+
*
|
|
206
|
+
* Read from the daemon's own inventory rather than assumed: it is the only
|
|
207
|
+
* party that knows whether the request reached it.
|
|
208
|
+
*/
|
|
209
|
+
export declare function jobStartedDespiteTimeout(daemonId: string, provider: string, credential?: unknown, opts?: {
|
|
210
|
+
now?: number;
|
|
211
|
+
windowMs?: number;
|
|
212
|
+
}): Promise<ManagedCliInstallJob | null>;
|
|
213
|
+
export declare function updateOutcomeLine(job: ManagedCliInstallJob): string | null;
|
|
165
214
|
export declare function readManagedCliInstallJob(daemonId: string, jobId: string, credential?: unknown): Promise<{
|
|
166
215
|
job?: ManagedCliInstallJob;
|
|
167
216
|
}>;
|
|
@@ -174,6 +223,16 @@ export declare function readManagedCliInstallJob(daemonId: string, jobId: string
|
|
|
174
223
|
* the end of what was already printed, and print only what follows it.
|
|
175
224
|
*/
|
|
176
225
|
export declare function unseenLogLines(printed: readonly string[], tail: readonly string[]): string[];
|
|
226
|
+
/**
|
|
227
|
+
* The new lines of a job's tail when the daemon reports its running line count:
|
|
228
|
+
* exactly the last `total − printedCount`, and how many of those already fell
|
|
229
|
+
* out of the window (the daemon keeps the last 200). Unlike overlap matching,
|
|
230
|
+
* this cannot mistake a repeated line for one already printed.
|
|
231
|
+
*/
|
|
232
|
+
export declare function newLogLinesByCount(printedCount: number, tail: readonly string[], total: number): {
|
|
233
|
+
lines: string[];
|
|
234
|
+
skipped: number;
|
|
235
|
+
};
|
|
177
236
|
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
178
237
|
export declare const INSTALL_MAX_WAIT_MS: number;
|
|
179
238
|
export declare const INSTALL_POLL_INTERVAL_MS = 2000;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = exports.ROW_READINESS_TIMEOUT_MS = exports.MANAGED_CLI_PROVIDER_SINCE = void 0;
|
|
3
|
+
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = exports.START_TIMEOUT_JOB_WINDOW_MS = exports.ROW_READINESS_TIMEOUT_MS = exports.MANAGED_CLI_PROVIDER_SINCE = void 0;
|
|
4
4
|
exports.describeProvidersNotOffered = describeProvidersNotOffered;
|
|
5
5
|
exports.providersNotOffered = providersNotOffered;
|
|
6
6
|
exports.listCliInstallers = listCliInstallers;
|
|
@@ -9,8 +9,12 @@ exports.readinessByProvider = readinessByProvider;
|
|
|
9
9
|
exports.readinessKey = readinessKey;
|
|
10
10
|
exports.readinessForRows = readinessForRows;
|
|
11
11
|
exports.startManagedCliInstall = startManagedCliInstall;
|
|
12
|
+
exports.startManagedCliUpdate = startManagedCliUpdate;
|
|
13
|
+
exports.jobStartedDespiteTimeout = jobStartedDespiteTimeout;
|
|
14
|
+
exports.updateOutcomeLine = updateOutcomeLine;
|
|
12
15
|
exports.readManagedCliInstallJob = readManagedCliInstallJob;
|
|
13
16
|
exports.unseenLogLines = unseenLogLines;
|
|
17
|
+
exports.newLogLinesByCount = newLogLinesByCount;
|
|
14
18
|
exports.followManagedCliInstallJob = followManagedCliInstallJob;
|
|
15
19
|
/**
|
|
16
20
|
* The daemon's managed-CLI installer inventory, as the CLI reads it.
|
|
@@ -85,12 +89,14 @@ function describeProvidersNotOffered(missing) {
|
|
|
85
89
|
.join(', ');
|
|
86
90
|
}
|
|
87
91
|
function providersNotOffered(offered, known) {
|
|
88
|
-
// Canonicalize BOTH sides.
|
|
89
|
-
//
|
|
90
|
-
// older spelling, so a raw comparison against the current
|
|
91
|
-
// a machine where it
|
|
92
|
-
//
|
|
93
|
-
//
|
|
92
|
+
// Canonicalize BOTH sides. A daemon answers under the spelling it was built
|
|
93
|
+
// with — during the rename window the 0.8.35 build reported the first-party
|
|
94
|
+
// harness under its older spelling, so a raw comparison against the current
|
|
95
|
+
// one reported it missing on a machine where it was installed and working (the
|
|
96
|
+
// first version of this helper said "Not offered: devin, <first-party
|
|
97
|
+
// harness>" when only `devin` was true). This is INGRESS, so it canonicalizes
|
|
98
|
+
// accepted spellings only: since contract a retired spelling is not the
|
|
99
|
+
// harness, and a daemon still answering under one predates the wire release.
|
|
94
100
|
const seen = new Set((offered ?? []).map((provider) => canonical(provider.provider)).filter(Boolean));
|
|
95
101
|
return known.filter((provider) => !seen.has(canonical(provider)));
|
|
96
102
|
}
|
|
@@ -211,6 +217,62 @@ async function readinessForRows(rows, credential, opts = {}) {
|
|
|
211
217
|
async function startManagedCliInstall(daemonId, provider, credential) {
|
|
212
218
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/install`, { method: 'POST', body: {}, credential: credential });
|
|
213
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* How long after a start request times out a job for the same provider is taken
|
|
222
|
+
* to be the one that request began.
|
|
223
|
+
*
|
|
224
|
+
* The relay holds the request while the daemon answers, and a gateway can cut
|
|
225
|
+
* that off at its own deadline: an update returned HTTP 504 after 21.9s while
|
|
226
|
+
* the job it started ran to success, and the caller was told the outcome was
|
|
227
|
+
* unknown (OSK-10096 verifier). A job that started within this window of the
|
|
228
|
+
* request is that request's job.
|
|
229
|
+
*/
|
|
230
|
+
exports.START_TIMEOUT_JOB_WINDOW_MS = 120_000;
|
|
231
|
+
/** Ask the daemon to update an installed CLI in place (OSK-10096). */
|
|
232
|
+
async function startManagedCliUpdate(daemonId, provider, credential) {
|
|
233
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/update`, { method: 'POST', body: {}, credential: credential });
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* How an update job ended, from the versions the daemon recorded on it:
|
|
237
|
+
* "Updated Claude Code 2.1.270 → 2.1.280." or "Claude Code is still 2.1.270:
|
|
238
|
+
* its updater found nothing newer." Null when the job does not carry both.
|
|
239
|
+
*/
|
|
240
|
+
/**
|
|
241
|
+
* The job a timed-out start request began, or null when none can be claimed.
|
|
242
|
+
*
|
|
243
|
+
* Read from the daemon's own inventory rather than assumed: it is the only
|
|
244
|
+
* party that knows whether the request reached it.
|
|
245
|
+
*/
|
|
246
|
+
async function jobStartedDespiteTimeout(daemonId, provider, credential, opts = {}) {
|
|
247
|
+
let response;
|
|
248
|
+
try {
|
|
249
|
+
response = await listCliInstallers(daemonId, credential);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const row = (response.providers ?? []).find((entry) => canonical(entry.provider) === canonical(provider));
|
|
255
|
+
const job = row?.job;
|
|
256
|
+
if (!job?.id)
|
|
257
|
+
return null;
|
|
258
|
+
if (job.status === 'queued' || job.status === 'running')
|
|
259
|
+
return job;
|
|
260
|
+
const startedAt = job.startedAt ? Date.parse(job.startedAt) : NaN;
|
|
261
|
+
if (!Number.isFinite(startedAt))
|
|
262
|
+
return null;
|
|
263
|
+
const now = opts.now ?? Date.now();
|
|
264
|
+
return now - startedAt <= (opts.windowMs ?? exports.START_TIMEOUT_JOB_WINDOW_MS) ? job : null;
|
|
265
|
+
}
|
|
266
|
+
function updateOutcomeLine(job) {
|
|
267
|
+
const before = job.previousVersion;
|
|
268
|
+
const after = job.installedVersion;
|
|
269
|
+
if (!before || !after)
|
|
270
|
+
return null;
|
|
271
|
+
const name = job.displayName || job.provider || 'The CLI';
|
|
272
|
+
return before === after
|
|
273
|
+
? `${name} is still ${after}: its updater found nothing newer.`
|
|
274
|
+
: `Updated ${name} ${before} → ${after}.`;
|
|
275
|
+
}
|
|
214
276
|
async function readManagedCliInstallJob(daemonId, jobId, credential) {
|
|
215
277
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/jobs/${encodeURIComponent(jobId)}`, { credential: credential });
|
|
216
278
|
}
|
|
@@ -237,6 +299,20 @@ function unseenLogLines(printed, tail) {
|
|
|
237
299
|
}
|
|
238
300
|
return [...tail];
|
|
239
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* The new lines of a job's tail when the daemon reports its running line count:
|
|
304
|
+
* exactly the last `total − printedCount`, and how many of those already fell
|
|
305
|
+
* out of the window (the daemon keeps the last 200). Unlike overlap matching,
|
|
306
|
+
* this cannot mistake a repeated line for one already printed.
|
|
307
|
+
*/
|
|
308
|
+
function newLogLinesByCount(printedCount, tail, total) {
|
|
309
|
+
const unseen = Math.max(0, total - printedCount);
|
|
310
|
+
if (unseen === 0)
|
|
311
|
+
return { lines: [], skipped: 0 };
|
|
312
|
+
if (unseen > tail.length)
|
|
313
|
+
return { lines: [...tail], skipped: unseen - tail.length };
|
|
314
|
+
return { lines: tail.slice(tail.length - unseen), skipped: 0 };
|
|
315
|
+
}
|
|
240
316
|
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
241
317
|
exports.INSTALL_MAX_WAIT_MS = 10 * 60_000;
|
|
242
318
|
exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
@@ -256,6 +332,7 @@ exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
|
256
332
|
async function followManagedCliInstallJob(input) {
|
|
257
333
|
const deadline = Date.now() + (input.maxWaitMs ?? exports.INSTALL_MAX_WAIT_MS);
|
|
258
334
|
let printed = [];
|
|
335
|
+
let printedCount = 0;
|
|
259
336
|
for (;;) {
|
|
260
337
|
const res = await readManagedCliInstallJob(input.daemonId, input.jobId, input.credential);
|
|
261
338
|
const job = res.job;
|
|
@@ -264,7 +341,20 @@ async function followManagedCliInstallJob(input) {
|
|
|
264
341
|
if (input.onLogLine) {
|
|
265
342
|
// `logTail`, the field the daemon sends — this read `log`, which no daemon
|
|
266
343
|
// has ever sent, so every install printed nothing until it ended (OSK-10103).
|
|
267
|
-
const
|
|
344
|
+
const tail = job.logTail ?? job.log ?? [];
|
|
345
|
+
let fresh;
|
|
346
|
+
if (typeof job.logLineCount === 'number') {
|
|
347
|
+
// Exact: the daemon says how many lines exist in total (OSK-10179).
|
|
348
|
+
const { lines, skipped } = newLogLinesByCount(printedCount, tail, job.logLineCount);
|
|
349
|
+
if (skipped > 0) {
|
|
350
|
+
input.onLogLine(`(… ${skipped} log line${skipped === 1 ? '' : 's'} not shown)`);
|
|
351
|
+
}
|
|
352
|
+
fresh = lines;
|
|
353
|
+
printedCount = Math.max(printedCount, job.logLineCount);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
fresh = unseenLogLines(printed, tail);
|
|
357
|
+
}
|
|
268
358
|
for (const line of fresh)
|
|
269
359
|
input.onLogLine(line);
|
|
270
360
|
printed = [...printed, ...fresh].slice(-MAX_TRACKED_LOG_LINES);
|
|
@@ -845,8 +845,10 @@ function selectDedicatedAttachHarness(lease, harnesses, engine) {
|
|
|
845
845
|
}
|
|
846
846
|
const providers = [...new Set(rows.map((row) => row.provider))];
|
|
847
847
|
if (engine) {
|
|
848
|
-
// Any
|
|
849
|
-
// harness's older name means the row stored
|
|
848
|
+
// Any ACCEPTED spelling of an engine names it — during a rename's alias window,
|
|
849
|
+
// a user who typed the first-party harness's older name means the row stored
|
|
850
|
+
// under its canonical one. A retired spelling is accepted nowhere, so it names
|
|
851
|
+
// no engine here either (`isSameHarnessProvider`).
|
|
850
852
|
const match = rows.find((row) => (0, auth_core_1.isSameHarnessProvider)(row.provider, engine));
|
|
851
853
|
if (!match) {
|
|
852
854
|
return {
|
package/dist/lib/file-mime.js
CHANGED
|
@@ -220,7 +220,7 @@ async function resolveFirstPartyHarnessAgent(explicit, deps = {}, options = {})
|
|
|
220
220
|
const created = options.createDefault === false
|
|
221
221
|
? null
|
|
222
222
|
: await (deps.ensureDefaultAgent ?? ensureDefaultAgent)(
|
|
223
|
-
// Canonical: what the server stores
|
|
223
|
+
// Canonical: what the server stores, and the one spelling every server accepts.
|
|
224
224
|
auth_core_1.FIRST_PARTY_HARNESS.provider, options.signal);
|
|
225
225
|
if (created?.id) {
|
|
226
226
|
return {
|
|
@@ -330,11 +330,12 @@ function readRawConfigKey() {
|
|
|
330
330
|
* first real session read. Returns whether the file now holds it under the
|
|
331
331
|
* neutral key.
|
|
332
332
|
*
|
|
333
|
-
* Reading alone used to leave it where it was
|
|
334
|
-
*
|
|
335
|
-
*
|
|
333
|
+
* Reading alone used to leave it where it was. `legacyAgentConfigKeys()` keeps
|
|
334
|
+
* naming an old key after its spelling is retired (it reads the identity's
|
|
335
|
+
* ARTIFACT spellings, since the file predates the rename), but a key that is only
|
|
336
|
+
* ever read is one the next pruning of `formerSpellings` would strand, and a user
|
|
336
337
|
* who never ran `--set`, `--clear` or logout would silently lose their setting.
|
|
337
|
-
* Moving it
|
|
338
|
+
* Moving it on the first session read retires the old key long before that.
|
|
338
339
|
*
|
|
339
340
|
* Only the session path calls this — `skrr code doctor` reports and never
|
|
340
341
|
* writes. Best-effort: an unwritable config is left alone and still read.
|
|
@@ -42,17 +42,6 @@ const first_party_harness_1 = require("./first-party-harness");
|
|
|
42
42
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
43
43
|
const harness_tiers_1 = require("./harness-tiers");
|
|
44
44
|
const first_party_harness_managed_1 = require("./first-party-harness-managed");
|
|
45
|
-
/**
|
|
46
|
-
* The variable one purpose was read from, and — when that was an older name — the
|
|
47
|
-
* sentence that says so. `undefined` when the neutral name (or nothing) was used.
|
|
48
|
-
*/
|
|
49
|
-
function legacyEnvNote(key, name) {
|
|
50
|
-
const neutral = auth_core_1.FIRST_PARTY_HARNESS_ENV[key].name;
|
|
51
|
-
if (!name || name === neutral)
|
|
52
|
-
return undefined;
|
|
53
|
-
return (`${name} is an older name for this setting and is still read; ` +
|
|
54
|
-
`rename it to ${neutral}, which wins when both are set.`);
|
|
55
|
-
}
|
|
56
45
|
/**
|
|
57
46
|
* Project-level state dir — the ENGINE's own project marker
|
|
58
47
|
* (`FIRST_PARTY_HARNESS_ENGINE.projectDirectory`). Basename deliberately differs
|
|
@@ -78,10 +67,12 @@ function projectStateDir(cwd) {
|
|
|
78
67
|
* the answer one copy-paste away and keeps the property — and the promise is
|
|
79
68
|
* reworded to match what is delivered rather than the other way round.
|
|
80
69
|
*
|
|
81
|
-
* The URL is the CANONICAL feed candidate.
|
|
82
|
-
* each
|
|
83
|
-
*
|
|
84
|
-
*
|
|
70
|
+
* The URL is the CANONICAL feed candidate. While a rename's alias window is open
|
|
71
|
+
* a manifest may also be published under each alias (`firstPartyHarnessFeedCandidates`
|
|
72
|
+
* for a channel read), and an installer that finds nothing at the canonical URL
|
|
73
|
+
* tries those next — so the remedy names them too. A retired spelling is not
|
|
74
|
+
* among them: its channel pointers stopped moving when publishing stopped writing
|
|
75
|
+
* them, and naming one would point at a stale release. Without that, "the canonical URL 404s" would read as "there is no release"
|
|
85
76
|
* on a feed an older pipeline published under the other spelling. The doctor
|
|
86
77
|
* cannot say which candidate actually holds a release, because it does not probe;
|
|
87
78
|
* it says where to look, in the order an installer looks.
|
|
@@ -97,15 +88,13 @@ function releaseChannelCheck(env) {
|
|
|
97
88
|
manifestFilename: candidate.manifestFilename,
|
|
98
89
|
channel,
|
|
99
90
|
}));
|
|
100
|
-
const legacyNote = legacyEnvNote('channel', source);
|
|
101
91
|
if (invalid) {
|
|
102
92
|
return {
|
|
103
93
|
name: 'release-channel',
|
|
104
94
|
status: 'warn',
|
|
105
95
|
detail: `${source}='${invalid}' is not a channel; following '${channel}' instead — ${canonical}`,
|
|
106
96
|
remedy: `Set ${auth_core_1.FIRST_PARTY_HARNESS_ENV.channel.name} to one of ${auth_core_1.FIRST_PARTY_HARNESS_CHANNELS.join(', ')}, ` +
|
|
107
|
-
'or unset it to follow the default.'
|
|
108
|
-
(legacyNote ? ` ${legacyNote}` : ''),
|
|
97
|
+
'or unset it to follow the default.',
|
|
109
98
|
};
|
|
110
99
|
}
|
|
111
100
|
const alsoTried = alternates.filter((url) => url !== canonical);
|
|
@@ -114,10 +103,7 @@ function releaseChannelCheck(env) {
|
|
|
114
103
|
status: 'ok',
|
|
115
104
|
detail: `${channel} — ${canonical}`,
|
|
116
105
|
remedy: 'Fetch that URL to see whether the channel has a published release, and which version.' +
|
|
117
|
-
(alsoTried.length
|
|
118
|
-
? ` If it has none, an installer also tries ${alsoTried.join(', ')}.`
|
|
119
|
-
: '') +
|
|
120
|
-
(legacyNote ? ` ${legacyNote}` : ''),
|
|
106
|
+
(alsoTried.length ? ` If it has none, an installer also tries ${alsoTried.join(', ')}.` : ''),
|
|
121
107
|
};
|
|
122
108
|
}
|
|
123
109
|
/**
|
|
@@ -159,7 +145,8 @@ function strandedLegacyEngine(env) {
|
|
|
159
145
|
/**
|
|
160
146
|
* Whether a managed engine resolved under an OLDER spelling of its file name.
|
|
161
147
|
*
|
|
162
|
-
* Not a fault: resolution tries every spelling on purpose
|
|
148
|
+
* Not a fault: resolution tries every artifact spelling on purpose (a former one
|
|
149
|
+
* included — the file predates the rename), and the next
|
|
163
150
|
* `skrr code` run renames the file onto the canonical name (leaving a link at the
|
|
164
151
|
* old one). Said anyway, because a path that does not match the name everything
|
|
165
152
|
* else prints is exactly the surprise this module exists to explain.
|
|
@@ -196,14 +183,12 @@ function engineCheck(env) {
|
|
|
196
183
|
};
|
|
197
184
|
}
|
|
198
185
|
if (bin && source === 'env') {
|
|
199
|
-
// Name the variable, not just "env":
|
|
200
|
-
// and the
|
|
201
|
-
const legacyNote = resolution.legacyEnv ? legacyEnvNote('path', resolution.envName) : undefined;
|
|
186
|
+
// Name the variable, not just "env": an override is the one resolution the
|
|
187
|
+
// user set by hand, and the variable is what they would edit.
|
|
202
188
|
return {
|
|
203
189
|
name: 'engine',
|
|
204
190
|
status: 'ok',
|
|
205
191
|
detail: `${bin} (resolved via env ${resolution.envName})`,
|
|
206
|
-
...(legacyNote ? { remedy: legacyNote } : {}),
|
|
207
192
|
};
|
|
208
193
|
}
|
|
209
194
|
if (bin) {
|
|
@@ -232,7 +217,6 @@ function engineCheck(env) {
|
|
|
232
217
|
};
|
|
233
218
|
}
|
|
234
219
|
const override = (0, auth_core_1.readFirstPartyHarnessEnv)(env, 'path');
|
|
235
|
-
const legacyNote = legacyEnvNote('path', override.name);
|
|
236
220
|
if (override.value && !node_path_1.default.isAbsolute(override.value)) {
|
|
237
221
|
return {
|
|
238
222
|
name: 'engine',
|
|
@@ -241,7 +225,7 @@ function engineCheck(env) {
|
|
|
241
225
|
// agent-invoked command the cwd is attacker-influenced, so a repo
|
|
242
226
|
// containing a file named like the engine could otherwise hijack it.
|
|
243
227
|
detail: `${override.name} is set to a relative path ('${override.value}') and was ignored`,
|
|
244
|
-
remedy: `Set ${pathVar} to an absolute path
|
|
228
|
+
remedy: `Set ${pathVar} to an absolute path.`,
|
|
245
229
|
};
|
|
246
230
|
}
|
|
247
231
|
if (override.value) {
|
|
@@ -249,8 +233,7 @@ function engineCheck(env) {
|
|
|
249
233
|
name: 'engine',
|
|
250
234
|
status: 'fail',
|
|
251
235
|
detail: `${override.name}='${override.value}' is not an executable file`,
|
|
252
|
-
remedy: `Point it at a real ${auth_core_1.FIRST_PARTY_HARNESS.displayName} engine build, or unset it to fall back to PATH
|
|
253
|
-
(legacyNote ? ` ${legacyNote}` : ''),
|
|
236
|
+
remedy: `Point it at a real ${auth_core_1.FIRST_PARTY_HARNESS.displayName} engine build, or unset it to fall back to PATH.`,
|
|
254
237
|
};
|
|
255
238
|
}
|
|
256
239
|
return {
|
|
@@ -451,7 +434,7 @@ function opencodeCheck(cwd) {
|
|
|
451
434
|
* `daemon/src/harness-trust.ts` again.
|
|
452
435
|
*/
|
|
453
436
|
function trustCheck() {
|
|
454
|
-
// The canonical slug. The shared table carries every spelling at the same tier,
|
|
437
|
+
// The canonical slug. The shared table carries every accepted spelling at the same tier,
|
|
455
438
|
// so which one is asked does not change the answer — but the report prints the
|
|
456
439
|
// name the platform stores.
|
|
457
440
|
const harness = auth_core_1.FIRST_PARTY_HARNESS.provider;
|
|
@@ -70,10 +70,15 @@ export declare const AGENT_FLAG_SPELLINGS: readonly ["--skrr-agent", "--oversky-
|
|
|
70
70
|
export declare const AGENT_CONFIG_KEY: "firstPartyHarnessAgentId";
|
|
71
71
|
/**
|
|
72
72
|
* Keys earlier CLIs stored the agent setting under: `<camelCased spelling>AgentId`
|
|
73
|
-
* for every spelling of the harness, canonical first.
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
73
|
+
* for every ARTIFACT spelling of the harness, canonical first.
|
|
74
|
+
*
|
|
75
|
+
* A config file on a user's disk is an artifact: a CLI that last ran before a
|
|
76
|
+
* rename wrote the key under the product name it had then, and that file does not
|
|
77
|
+
* change when the spelling stops being accepted anywhere. So the key is derived
|
|
78
|
+
* from `firstPartyHarnessArtifactSpellings` — former spellings included — and is
|
|
79
|
+
* read (and moved onto the neutral key) for as long as the identity remembers the
|
|
80
|
+
* spelling, not merely for as long as some ingress still accepts it. Derived
|
|
81
|
+
* rather than listed, so no product name is written here.
|
|
77
82
|
*/
|
|
78
83
|
export declare function legacyAgentConfigKeys(): string[];
|
|
79
84
|
export interface AgentSetting {
|
|
@@ -90,17 +90,22 @@ exports.AGENT_CONFIG_KEY = 'firstPartyHarnessAgentId';
|
|
|
90
90
|
function camelCaseSlug(slug) {
|
|
91
91
|
return slug.replace(/-([a-z0-9])/g, (_, next) => next.toUpperCase());
|
|
92
92
|
}
|
|
93
|
-
// TODO(identity): move to auth-core — the identity module
|
|
94
|
-
// names; it has no notion of a legacy CONFIG key yet.
|
|
93
|
+
// TODO(identity): move to auth-core — the identity module derives on-disk binary
|
|
94
|
+
// names from its artifact spellings; it has no notion of a legacy CONFIG key yet.
|
|
95
95
|
/**
|
|
96
96
|
* Keys earlier CLIs stored the agent setting under: `<camelCased spelling>AgentId`
|
|
97
|
-
* for every spelling of the harness, canonical first.
|
|
97
|
+
* for every ARTIFACT spelling of the harness, canonical first.
|
|
98
98
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* A config file on a user's disk is an artifact: a CLI that last ran before a
|
|
100
|
+
* rename wrote the key under the product name it had then, and that file does not
|
|
101
|
+
* change when the spelling stops being accepted anywhere. So the key is derived
|
|
102
|
+
* from `firstPartyHarnessArtifactSpellings` — former spellings included — and is
|
|
103
|
+
* read (and moved onto the neutral key) for as long as the identity remembers the
|
|
104
|
+
* spelling, not merely for as long as some ingress still accepts it. Derived
|
|
105
|
+
* rather than listed, so no product name is written here.
|
|
101
106
|
*/
|
|
102
107
|
function legacyAgentConfigKeys() {
|
|
103
|
-
return (0, auth_core_1.
|
|
108
|
+
return (0, auth_core_1.firstPartyHarnessArtifactSpellings)().map((spelling) => `${camelCaseSlug(spelling)}AgentId`);
|
|
104
109
|
}
|
|
105
110
|
/** Read the setting from a config record: the neutral key first, then each older key. */
|
|
106
111
|
function readAgentSetting(config) {
|
|
@@ -250,8 +255,7 @@ function managedSkipReason(env = process.env) {
|
|
|
250
255
|
announce: false,
|
|
251
256
|
};
|
|
252
257
|
}
|
|
253
|
-
// The operator's opt-OUT (`FIRST_PARTY_HARNESS_ENV.managed
|
|
254
|
-
// older name second). Present because "run with my own key" has to be
|
|
258
|
+
// The operator's opt-OUT (`FIRST_PARTY_HARNESS_ENV.managed`). Present because "run with my own key" has to be
|
|
255
259
|
// expressible without logging out, and because a test that asserts argv
|
|
256
260
|
// forwarding must be able to say "this run is not about acquisition" rather
|
|
257
261
|
// than depend on whether the machine happens to be signed in.
|
|
@@ -259,8 +263,7 @@ function managedSkipReason(env = process.env) {
|
|
|
259
263
|
if (envFlagIsOff(managedFlag.value)) {
|
|
260
264
|
return {
|
|
261
265
|
kind: 'disabled',
|
|
262
|
-
// The variable that was actually read
|
|
263
|
-
// to change is the one that is set.
|
|
266
|
+
// The variable that was actually read, so the sentence names what to change.
|
|
264
267
|
detail: `${managedFlag.name} is off`,
|
|
265
268
|
announce: false,
|
|
266
269
|
};
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
* Every product-named value — the binary's file name, the engine home, the env
|
|
19
19
|
* var names, the display name — is read from the identity module in
|
|
20
20
|
* `@skrr-ai/auth-core` (`docs/architecture/skrr-code-identifier-rename-2026-09-13.md`).
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* An installed engine is an ARTIFACT: one installed before a rename is still on
|
|
22
|
+
* disk under its old file name after that name stops being accepted anywhere, so
|
|
23
|
+
* resolution tries every artifact spelling (`firstPartyHarnessArtifactSpellings`),
|
|
24
|
+
* canonical first, in each location. That recognises a file; it admits no input.
|
|
23
25
|
*/
|
|
24
26
|
/**
|
|
25
27
|
* The error `execEngine` throws when no engine resolves. A value the commands
|
|
@@ -44,11 +46,12 @@ export declare function engineHome(env?: NodeJS.ProcessEnv): string;
|
|
|
44
46
|
/**
|
|
45
47
|
* The user-level instruction root the engine owns.
|
|
46
48
|
*
|
|
47
|
-
* Read under the neutral variable first
|
|
48
|
-
* (`
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* engine
|
|
49
|
+
* Read under the platform's neutral variable first, then under the variable the
|
|
50
|
+
* ENGINE itself reads (`FIRST_PARTY_HARNESS_ENGINE.env.home`). The second is not a
|
|
51
|
+
* legacy fallback: it is the engine's own name, the engine honours it whatever the
|
|
52
|
+
* platform does, and a CLI that ignored it would report one instruction root while
|
|
53
|
+
* the engine it launches used another. {@link engineOwnedEnv} closes the other
|
|
54
|
+
* direction, handing a neutral-variable value to the engine under its own name.
|
|
52
55
|
*/
|
|
53
56
|
export declare function engineInstructionHome(env?: NodeJS.ProcessEnv): string;
|
|
54
57
|
/**
|
|
@@ -58,8 +61,9 @@ export declare function engineInstructionHome(env?: NodeJS.ProcessEnv): string;
|
|
|
58
61
|
*/
|
|
59
62
|
export declare function managedEnginePath(env?: NodeJS.ProcessEnv, provider?: string): string;
|
|
60
63
|
/**
|
|
61
|
-
* The same managed location under the PREVIOUS config root, for every
|
|
62
|
-
* the binary and every engine home name,
|
|
64
|
+
* The same managed location under the PREVIOUS config root, for every artifact
|
|
65
|
+
* spelling of the binary (former spellings included) and every engine home name,
|
|
66
|
+
* canonical first.
|
|
63
67
|
*
|
|
64
68
|
* The root moved `~/.oversky` → `~/.skrr` in the skrr rename, and an engine
|
|
65
69
|
* installed before that is still on disk under the old one. `daemon/src/
|
|
@@ -69,7 +73,7 @@ export declare function managedEnginePath(env?: NodeJS.ProcessEnv, provider?: st
|
|
|
69
73
|
*
|
|
70
74
|
* The engine-home move (`migrateFirstPartyHarnessHome`) only ever works under the
|
|
71
75
|
* CURRENT root, so nothing renames a binary here — which is why this list carries
|
|
72
|
-
* every spelling rather than only the canonical one.
|
|
76
|
+
* every artifact spelling rather than only the canonical one.
|
|
73
77
|
*
|
|
74
78
|
* Empty when an explicit `OVERSKY_CONFIG_DIR` is set, matching the daemon: that
|
|
75
79
|
* override names one root deliberately, and reaching past it to a hard-coded home
|
|
@@ -84,11 +88,9 @@ export interface EngineResolution {
|
|
|
84
88
|
/**
|
|
85
89
|
* The variable an explicit override was read from — set whenever one was set,
|
|
86
90
|
* including an override that was refused. The doctor names it, because an
|
|
87
|
-
* operator fixing a bad override needs to
|
|
91
|
+
* operator fixing a bad override needs to be told which variable to change.
|
|
88
92
|
*/
|
|
89
93
|
envName?: string;
|
|
90
|
-
/** True when {@link envName} is an older name for the override. */
|
|
91
|
-
legacyEnv?: boolean;
|
|
92
94
|
}
|
|
93
95
|
/**
|
|
94
96
|
* Resolve the engine binary, reporting WHERE it came from.
|
|
@@ -100,12 +102,12 @@ export interface EngineResolution {
|
|
|
100
102
|
* The env override is checked first and is absolute-only. A relative override
|
|
101
103
|
* would resolve against the caller's cwd, which for an agent-invoked command is
|
|
102
104
|
* attacker-influenced: a repo containing a file named like the engine could
|
|
103
|
-
* hijack it.
|
|
105
|
+
* hijack it.
|
|
104
106
|
*
|
|
105
107
|
* **Order: env → managed → PATH → well-known (WL-6.2).** Within each location
|
|
106
|
-
* every spelling of the binary is tried, canonical first — the daemon's
|
|
107
|
-
* so an engine installed under an older name still resolves,
|
|
108
|
-
* one wins wherever both exist.
|
|
108
|
+
* every artifact spelling of the binary is tried, canonical first — the daemon's
|
|
109
|
+
* order — so an engine installed under an older or former name still resolves,
|
|
110
|
+
* and the canonical one wins wherever both exist.
|
|
109
111
|
*
|
|
110
112
|
* The managed location beating `PATH` is the load-bearing part, and it is a
|
|
111
113
|
* deliberate reversal of the obvious ordering.
|
|
@@ -376,10 +378,12 @@ export declare function engineSpawnEnv(env: NodeJS.ProcessEnv, extraEnv: Record<
|
|
|
376
378
|
* `skrr code doctor` honour it while the engine it launches ignored it — two
|
|
377
379
|
* answers to "where do my instructions live" from one command.
|
|
378
380
|
*
|
|
379
|
-
* Only when the
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
381
|
+
* Only when the neutral variable supplied the value: a value set only under the
|
|
382
|
+
* engine's own name is inherited already, and rewriting it would only replace the
|
|
383
|
+
* user's spelling of a path with ours. When both are set the neutral one wins, as
|
|
384
|
+
* it does in {@link engineInstructionHome}, so the engine is handed that one. The
|
|
385
|
+
* resolved path is passed rather than the raw value, so the engine and the doctor
|
|
386
|
+
* agree on `~` expansion too.
|
|
383
387
|
*
|
|
384
388
|
* Not a credential and never one: it cannot reintroduce anything the sanitizer
|
|
385
389
|
* strips, and `extraEnv` still layers over it.
|