@skrr-ai/cli 0.1.42 → 0.1.44
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 +2 -0
- package/dist/base-command.js +1 -0
- package/dist/commands/balance/index.js +1 -1
- package/dist/commands/balance/overage.js +4 -3
- package/dist/commands/balance/plan.js +10 -6
- package/dist/commands/balance/show.d.ts +32 -0
- package/dist/commands/balance/show.js +74 -3
- 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/handover.d.ts +1 -0
- package/dist/commands/code/handover.js +4 -0
- package/dist/commands/code/install.js +3 -2
- package/dist/commands/code/jobs/run.d.ts +1 -0
- package/dist/commands/code/jobs/run.js +4 -0
- package/dist/commands/daemon/install.js +7 -0
- package/dist/commands/harnesses/install.d.ts +11 -0
- package/dist/commands/harnesses/install.js +33 -14
- package/dist/commands/harnesses/installers.d.ts +10 -0
- package/dist/commands/harnesses/installers.js +31 -0
- package/dist/commands/harnesses/leases/show.js +14 -0
- package/dist/commands/harnesses/list.js +3 -2
- package/dist/commands/instructions/install.d.ts +22 -0
- package/dist/commands/instructions/install.js +83 -7
- package/dist/commands/instructions/list.js +5 -0
- package/dist/commands/instructions/show.d.ts +6 -0
- package/dist/commands/instructions/show.js +34 -1
- package/dist/commands/instructions/status.js +17 -1
- package/dist/commands/payments/wallet.js +2 -2
- 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 +56 -1
- package/dist/commands/tasks/create.d.ts +6 -0
- package/dist/commands/tasks/create.js +26 -3
- package/dist/commands/tasks/labels/attach.js +4 -0
- package/dist/commands/tasks/list.d.ts +1 -0
- package/dist/commands/tasks/list.js +9 -0
- package/dist/commands/tasks/result/submit.d.ts +3 -0
- package/dist/commands/tasks/result/submit.js +76 -8
- package/dist/commands/tasks/show.d.ts +23 -0
- package/dist/commands/tasks/show.js +61 -1
- package/dist/commands/tasks/update.js +16 -2
- package/dist/commands/tasks/updates/add.d.ts +1 -0
- package/dist/commands/tasks/updates/add.js +63 -0
- package/dist/commands/views/create.js +12 -2
- package/dist/commands/views/list.js +3 -2
- package/dist/commands/views/show.js +2 -0
- package/dist/lib/agentic-stream.d.ts +10 -4
- package/dist/lib/agentic-stream.js +25 -11
- package/dist/lib/api-fetch.js +7 -2
- package/dist/lib/cli-installers.d.ts +23 -4
- package/dist/lib/cli-installers.js +56 -8
- package/dist/lib/daemon-setup.d.ts +18 -1
- package/dist/lib/daemon-setup.js +33 -1
- package/dist/lib/dedicated-machines.js +4 -2
- package/dist/lib/file-mime.js +1 -1
- package/dist/lib/first-party-harness-agent.d.ts +16 -1
- package/dist/lib/first-party-harness-agent.js +47 -17
- package/dist/lib/first-party-harness-doctor.js +49 -48
- 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 +32 -21
- package/dist/lib/first-party-harness.js +49 -31
- 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/harnesses.d.ts +6 -0
- package/dist/lib/html-text.js +25 -0
- package/dist/lib/instruction-input.d.ts +21 -0
- package/dist/lib/instruction-input.js +30 -0
- package/dist/lib/instruction-provenance.d.ts +54 -0
- package/dist/lib/instruction-provenance.js +84 -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 +15 -0
- package/dist/lib/task-closure.js +20 -0
- package/dist/lib/task-view-render.d.ts +14 -0
- package/dist/lib/task-view-render.js +55 -0
- package/dist/lib/tasks.d.ts +26 -0
- package/dist/lib/tasks.js +38 -1
- package/dist/lib/views/vocabulary.d.ts +1 -1
- package/dist/lib/views/vocabulary.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +183 -58
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +286 -100
- 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/cjs/legacyStatePreflight.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.js +75 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +183 -58
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +279 -99
- 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/dist/esm/legacyStatePreflight.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.js +74 -19
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4588 -4509
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +33980 -33832
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const format_1 = require("../../lib/format");
|
|
7
|
+
const task_view_render_1 = require("../../lib/task-view-render");
|
|
7
8
|
const web_url_1 = require("../../lib/web-url");
|
|
8
9
|
class ViewsList extends base_command_1.BaseCommand {
|
|
9
10
|
static description = 'List personalized Task Views';
|
|
@@ -30,13 +31,13 @@ class ViewsList extends base_command_1.BaseCommand {
|
|
|
30
31
|
name: view.name,
|
|
31
32
|
type: view.viewType,
|
|
32
33
|
group: view.groupBy,
|
|
33
|
-
|
|
34
|
+
selection: (0, task_view_render_1.summarizeTaskViewSelection)(view),
|
|
34
35
|
id: view.id,
|
|
35
36
|
})), [
|
|
36
37
|
{ key: 'name', header: 'NAME', maxWidth: 36 },
|
|
37
38
|
{ key: 'type', header: 'TYPE' },
|
|
38
39
|
{ key: 'group', header: 'GROUP BY' },
|
|
39
|
-
{ key: '
|
|
40
|
+
{ key: 'selection', header: 'SELECTION', maxWidth: 48 },
|
|
40
41
|
{ key: 'id', header: 'ID' },
|
|
41
42
|
], (line) => this.log(line));
|
|
42
43
|
}
|
|
@@ -33,6 +33,8 @@ class ViewsShow extends base_command_1.BaseCommand {
|
|
|
33
33
|
this.log(line);
|
|
34
34
|
}
|
|
35
35
|
this.log(result.url);
|
|
36
|
+
// The criteria are half the answer; the tasks they select are the other.
|
|
37
|
+
this.log(`Tasks: ${this.config.bin} tasks list --view ${view.id}`);
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
exports.default = ViewsShow;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Why a question was dismissed when no terminal exists to ask it on. It is the
|
|
3
|
+
* REASON for a dismissal, never an answer: it travels as the value of the
|
|
4
|
+
* reserved `_dismissed` key, which every consumer already reads as "nobody
|
|
5
|
+
* answered" — the web card sends the same key when a person closes it.
|
|
6
|
+
*
|
|
7
|
+
* It used to be written into every question's answer slot (OSK-9767). That was
|
|
8
|
+
* harmless text for Claude's AskUserQuestion, but a Devin form elicitation read
|
|
9
|
+
* it as the field's value and was answered `accept` with this sentence filled in
|
|
10
|
+
* — consent nobody gave, on a form nobody saw (OSK-10274).
|
|
5
11
|
*/
|
|
6
|
-
export declare const
|
|
12
|
+
export declare const NON_INTERACTIVE_QUESTION_DISMISSAL: string;
|
|
7
13
|
export type AgenticStreamEvent = {
|
|
8
14
|
type: string;
|
|
9
15
|
sessionId?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AgenticStreamClient = exports.USER_CONNECTION_CAP_CODE = exports.
|
|
3
|
+
exports.AgenticStreamClient = exports.USER_CONNECTION_CAP_CODE = exports.NON_INTERACTIVE_QUESTION_DISMISSAL = void 0;
|
|
4
4
|
exports.writeJsonLine = writeJsonLine;
|
|
5
5
|
exports.envelopesText = envelopesText;
|
|
6
6
|
exports.envelopeKey = envelopeKey;
|
|
@@ -20,11 +20,17 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
20
20
|
const config_1 = require("./config");
|
|
21
21
|
const publicEndpoints_generated_1 = require("./publicEndpoints.generated");
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Why a question was dismissed when no terminal exists to ask it on. It is the
|
|
24
|
+
* REASON for a dismissal, never an answer: it travels as the value of the
|
|
25
|
+
* reserved `_dismissed` key, which every consumer already reads as "nobody
|
|
26
|
+
* answered" — the web card sends the same key when a person closes it.
|
|
27
|
+
*
|
|
28
|
+
* It used to be written into every question's answer slot (OSK-9767). That was
|
|
29
|
+
* harmless text for Claude's AskUserQuestion, but a Devin form elicitation read
|
|
30
|
+
* it as the field's value and was answered `accept` with this sentence filled in
|
|
31
|
+
* — consent nobody gave, on a form nobody saw (OSK-10274).
|
|
26
32
|
*/
|
|
27
|
-
exports.
|
|
33
|
+
exports.NON_INTERACTIVE_QUESTION_DISMISSAL = 'No answer: this turn was started non-interactively (no terminal), so nobody can reply to ' +
|
|
28
34
|
'questions. Proceed with your best judgement, or finish by stating exactly what you need.';
|
|
29
35
|
/**
|
|
30
36
|
* Write exactly one complete JSON value on one line.
|
|
@@ -710,13 +716,21 @@ class AgenticStreamClient {
|
|
|
710
716
|
// "Not answered … timed out", at the cost of the whole wait. Answer at once
|
|
711
717
|
// instead, saying why, so the agent proceeds or states what it needs. An
|
|
712
718
|
// explicit `input` (tests, an embedding caller) is treated as interactive.
|
|
719
|
+
//
|
|
720
|
+
// "At once" is a DISMISSAL, never an answer. Nothing goes in any question's
|
|
721
|
+
// answer slot: a harness that turns answers into structured consent (an ACP
|
|
722
|
+
// form elicitation) would otherwise accept whatever text sits there
|
|
723
|
+
// (OSK-10274). The reason rides on `_dismissed`, where the server maps it to
|
|
724
|
+
// a dismissal for daemon sessions and a cloud session still reads why.
|
|
713
725
|
const interactive = this.options.input !== undefined || Boolean(process.stdin.isTTY);
|
|
714
726
|
const answers = {};
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
727
|
+
if (interactive) {
|
|
728
|
+
for (const question of event.questions ?? []) {
|
|
729
|
+
answers[question.header ?? question.question] = await this.ask(`${question.header ?? 'Question'}: ${question.question}\n> `);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
answers._dismissed = exports.NON_INTERACTIVE_QUESTION_DISMISSAL;
|
|
720
734
|
}
|
|
721
735
|
if (event.questionId && this.socket) {
|
|
722
736
|
this.socket.emit('question:answer', {
|
|
@@ -824,7 +838,7 @@ class AgenticStreamClient {
|
|
|
824
838
|
this.renderedText = this.accumulated;
|
|
825
839
|
}
|
|
826
840
|
else if (event.type === 'question.answered' && event.nonInteractive) {
|
|
827
|
-
output.write('\n[question] no terminal to answer on, so the
|
|
841
|
+
output.write('\n[question] no terminal to answer on, so the question was dismissed unanswered\n');
|
|
828
842
|
this.renderedText = this.accumulated;
|
|
829
843
|
}
|
|
830
844
|
}
|
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) {
|
|
@@ -34,10 +34,7 @@ export interface ManagedCliProviderState {
|
|
|
34
34
|
updateDisabledReason?: string | null;
|
|
35
35
|
platformSupported?: boolean;
|
|
36
36
|
authentication?: ManagedCliAuthentication | null;
|
|
37
|
-
job?:
|
|
38
|
-
id?: string;
|
|
39
|
-
status?: string;
|
|
40
|
-
} | null;
|
|
37
|
+
job?: ManagedCliInstallJob | null;
|
|
41
38
|
}
|
|
42
39
|
export interface CliInstallersResponse {
|
|
43
40
|
daemonId?: string;
|
|
@@ -155,6 +152,7 @@ export interface ManagedCliInstallJob {
|
|
|
155
152
|
status: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
156
153
|
/** `update` for an in-place update; absent from an older daemon, meaning install. */
|
|
157
154
|
kind?: 'install' | 'update';
|
|
155
|
+
startedAt?: string;
|
|
158
156
|
previousVersion?: string | null;
|
|
159
157
|
installedVersion?: string | null;
|
|
160
158
|
error?: string;
|
|
@@ -184,6 +182,17 @@ export interface StartInstallResponse {
|
|
|
184
182
|
};
|
|
185
183
|
}
|
|
186
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;
|
|
187
196
|
/** Ask the daemon to update an installed CLI in place (OSK-10096). */
|
|
188
197
|
export declare function startManagedCliUpdate(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
|
|
189
198
|
/**
|
|
@@ -191,6 +200,16 @@ export declare function startManagedCliUpdate(daemonId: string, provider: string
|
|
|
191
200
|
* "Updated Claude Code 2.1.270 → 2.1.280." or "Claude Code is still 2.1.270:
|
|
192
201
|
* its updater found nothing newer." Null when the job does not carry both.
|
|
193
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>;
|
|
194
213
|
export declare function updateOutcomeLine(job: ManagedCliInstallJob): string | null;
|
|
195
214
|
export declare function readManagedCliInstallJob(daemonId: string, jobId: string, credential?: unknown): Promise<{
|
|
196
215
|
job?: ManagedCliInstallJob;
|
|
@@ -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;
|
|
@@ -10,6 +10,7 @@ exports.readinessKey = readinessKey;
|
|
|
10
10
|
exports.readinessForRows = readinessForRows;
|
|
11
11
|
exports.startManagedCliInstall = startManagedCliInstall;
|
|
12
12
|
exports.startManagedCliUpdate = startManagedCliUpdate;
|
|
13
|
+
exports.jobStartedDespiteTimeout = jobStartedDespiteTimeout;
|
|
13
14
|
exports.updateOutcomeLine = updateOutcomeLine;
|
|
14
15
|
exports.readManagedCliInstallJob = readManagedCliInstallJob;
|
|
15
16
|
exports.unseenLogLines = unseenLogLines;
|
|
@@ -88,12 +89,14 @@ function describeProvidersNotOffered(missing) {
|
|
|
88
89
|
.join(', ');
|
|
89
90
|
}
|
|
90
91
|
function providersNotOffered(offered, known) {
|
|
91
|
-
// Canonicalize BOTH sides.
|
|
92
|
-
//
|
|
93
|
-
// older spelling, so a raw comparison against the current
|
|
94
|
-
// a machine where it
|
|
95
|
-
//
|
|
96
|
-
//
|
|
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.
|
|
97
100
|
const seen = new Set((offered ?? []).map((provider) => canonical(provider.provider)).filter(Boolean));
|
|
98
101
|
return known.filter((provider) => !seen.has(canonical(provider)));
|
|
99
102
|
}
|
|
@@ -126,9 +129,17 @@ function readinessOf(provider) {
|
|
|
126
129
|
ok: false,
|
|
127
130
|
};
|
|
128
131
|
}
|
|
132
|
+
// Every other not-ready state tells the reader what to do next, so this one
|
|
133
|
+
// does too. Name the install command only when the daemon says it can
|
|
134
|
+
// install the provider. Otherwise the daemon's own reason, or the bare
|
|
135
|
+
// fact, is the whole answer.
|
|
136
|
+
const key = canonical(provider.provider);
|
|
129
137
|
return {
|
|
130
138
|
label: 'not-installed',
|
|
131
|
-
detail: provider.installDisabledReason ||
|
|
139
|
+
detail: provider.installDisabledReason ||
|
|
140
|
+
(provider.installable === true && key
|
|
141
|
+
? `No binary for this provider on this machine. Install it with \`skrr harnesses install ${key}\`.`
|
|
142
|
+
: 'No binary for this provider on this machine.'),
|
|
132
143
|
ok: false,
|
|
133
144
|
};
|
|
134
145
|
}
|
|
@@ -214,6 +225,17 @@ async function readinessForRows(rows, credential, opts = {}) {
|
|
|
214
225
|
async function startManagedCliInstall(daemonId, provider, credential) {
|
|
215
226
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/install`, { method: 'POST', body: {}, credential: credential });
|
|
216
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* How long after a start request times out a job for the same provider is taken
|
|
230
|
+
* to be the one that request began.
|
|
231
|
+
*
|
|
232
|
+
* The relay holds the request while the daemon answers, and a gateway can cut
|
|
233
|
+
* that off at its own deadline: an update returned HTTP 504 after 21.9s while
|
|
234
|
+
* the job it started ran to success, and the caller was told the outcome was
|
|
235
|
+
* unknown (OSK-10096 verifier). A job that started within this window of the
|
|
236
|
+
* request is that request's job.
|
|
237
|
+
*/
|
|
238
|
+
exports.START_TIMEOUT_JOB_WINDOW_MS = 120_000;
|
|
217
239
|
/** Ask the daemon to update an installed CLI in place (OSK-10096). */
|
|
218
240
|
async function startManagedCliUpdate(daemonId, provider, credential) {
|
|
219
241
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/update`, { method: 'POST', body: {}, credential: credential });
|
|
@@ -223,6 +245,32 @@ async function startManagedCliUpdate(daemonId, provider, credential) {
|
|
|
223
245
|
* "Updated Claude Code 2.1.270 → 2.1.280." or "Claude Code is still 2.1.270:
|
|
224
246
|
* its updater found nothing newer." Null when the job does not carry both.
|
|
225
247
|
*/
|
|
248
|
+
/**
|
|
249
|
+
* The job a timed-out start request began, or null when none can be claimed.
|
|
250
|
+
*
|
|
251
|
+
* Read from the daemon's own inventory rather than assumed: it is the only
|
|
252
|
+
* party that knows whether the request reached it.
|
|
253
|
+
*/
|
|
254
|
+
async function jobStartedDespiteTimeout(daemonId, provider, credential, opts = {}) {
|
|
255
|
+
let response;
|
|
256
|
+
try {
|
|
257
|
+
response = await listCliInstallers(daemonId, credential);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const row = (response.providers ?? []).find((entry) => canonical(entry.provider) === canonical(provider));
|
|
263
|
+
const job = row?.job;
|
|
264
|
+
if (!job?.id)
|
|
265
|
+
return null;
|
|
266
|
+
if (job.status === 'queued' || job.status === 'running')
|
|
267
|
+
return job;
|
|
268
|
+
const startedAt = job.startedAt ? Date.parse(job.startedAt) : NaN;
|
|
269
|
+
if (!Number.isFinite(startedAt))
|
|
270
|
+
return null;
|
|
271
|
+
const now = opts.now ?? Date.now();
|
|
272
|
+
return now - startedAt <= (opts.windowMs ?? exports.START_TIMEOUT_JOB_WINDOW_MS) ? job : null;
|
|
273
|
+
}
|
|
226
274
|
function updateOutcomeLine(job) {
|
|
227
275
|
const before = job.previousVersion;
|
|
228
276
|
const after = job.installedVersion;
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* whole area has been removing. So: asked when there is someone to ask, skipped
|
|
29
29
|
* with an instruction when there is not, and never on the opt-out.
|
|
30
30
|
*/
|
|
31
|
+
import { type LegacyStateReport } from '@skrr-ai/auth-core';
|
|
31
32
|
import { confirm } from './prompt';
|
|
32
33
|
export interface DaemonSetupOutcome {
|
|
33
34
|
status: 'installed' | 'declined' | 'skipped' | 'failed';
|
|
@@ -41,9 +42,25 @@ export interface DaemonSetupOptions {
|
|
|
41
42
|
confirmImpl?: typeof confirm;
|
|
42
43
|
interactive?: boolean;
|
|
43
44
|
log?: (line: string) => void;
|
|
45
|
+
/** Injected by tests. */
|
|
46
|
+
findLegacyStateImpl?: () => LegacyStateReport;
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Refuse, before anything is fetched or asked, on a machine still carrying state
|
|
50
|
+
* from the previous product identity.
|
|
51
|
+
*
|
|
52
|
+
* The runtime refuses the same machine in `skrrd install`, and it is right to —
|
|
53
|
+
* but that check can only run once there is a runtime to run it, so onboarding
|
|
54
|
+
* downloaded a release, asked "Set it up now?", and only then stopped (OSK-6895).
|
|
55
|
+
* The detection is a read of two directory names, so the CLI does it first and
|
|
56
|
+
* the refusal names `skrr daemon install`, the command the person actually uses.
|
|
57
|
+
* The runtime's own check stays: `skrrd install` can be run directly.
|
|
58
|
+
*
|
|
59
|
+
* Returns true when it refused, having printed why and what to run.
|
|
60
|
+
*/
|
|
61
|
+
export declare function refuseOnLegacyLocalState(log: (line: string) => void, findImpl?: () => LegacyStateReport): boolean;
|
|
45
62
|
/**
|
|
46
63
|
* Ask, then install. Returns without touching anything when there is nobody to
|
|
47
|
-
* ask
|
|
64
|
+
* ask, the operator has opted out, or the machine still needs its cutover.
|
|
48
65
|
*/
|
|
49
66
|
export declare function offerDaemonSetup(opts?: DaemonSetupOptions): Promise<DaemonSetupOutcome>;
|
package/dist/lib/daemon-setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refuseOnLegacyLocalState = refuseOnLegacyLocalState;
|
|
3
4
|
exports.offerDaemonSetup = offerDaemonSetup;
|
|
4
5
|
/**
|
|
5
6
|
* daemon-setup.ts — offer to set this machine up, at the moment we can.
|
|
@@ -31,15 +32,40 @@ exports.offerDaemonSetup = offerDaemonSetup;
|
|
|
31
32
|
* whole area has been removing. So: asked when there is someone to ask, skipped
|
|
32
33
|
* with an instruction when there is not, and never on the opt-out.
|
|
33
34
|
*/
|
|
35
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
34
36
|
const prompt_1 = require("./prompt");
|
|
35
37
|
const daemon_binding_1 = require("./daemon-binding");
|
|
36
38
|
const daemon_handoff_opt_out_1 = require("./daemon-handoff-opt-out");
|
|
37
39
|
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
38
40
|
const daemon_installer_1 = require("./daemon-installer");
|
|
39
41
|
const ssh_detect_1 = require("./ssh-detect");
|
|
42
|
+
/**
|
|
43
|
+
* Refuse, before anything is fetched or asked, on a machine still carrying state
|
|
44
|
+
* from the previous product identity.
|
|
45
|
+
*
|
|
46
|
+
* The runtime refuses the same machine in `skrrd install`, and it is right to —
|
|
47
|
+
* but that check can only run once there is a runtime to run it, so onboarding
|
|
48
|
+
* downloaded a release, asked "Set it up now?", and only then stopped (OSK-6895).
|
|
49
|
+
* The detection is a read of two directory names, so the CLI does it first and
|
|
50
|
+
* the refusal names `skrr daemon install`, the command the person actually uses.
|
|
51
|
+
* The runtime's own check stays: `skrrd install` can be run directly.
|
|
52
|
+
*
|
|
53
|
+
* Returns true when it refused, having printed why and what to run.
|
|
54
|
+
*/
|
|
55
|
+
function refuseOnLegacyLocalState(log, findImpl = auth_core_1.findLegacyLocalState) {
|
|
56
|
+
const report = findImpl();
|
|
57
|
+
if (!report.hasLegacyState)
|
|
58
|
+
return false;
|
|
59
|
+
log('');
|
|
60
|
+
for (const line of (0, auth_core_1.describeLegacyState)(report, 'skrr daemon').split('\n')) {
|
|
61
|
+
log(line ? ` ${line}` : '');
|
|
62
|
+
}
|
|
63
|
+
log('');
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
40
66
|
/**
|
|
41
67
|
* Ask, then install. Returns without touching anything when there is nobody to
|
|
42
|
-
* ask
|
|
68
|
+
* ask, the operator has opted out, or the machine still needs its cutover.
|
|
43
69
|
*/
|
|
44
70
|
async function offerDaemonSetup(opts = {}) {
|
|
45
71
|
const env = opts.env ?? process.env;
|
|
@@ -51,6 +77,12 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
51
77
|
if (optOut) {
|
|
52
78
|
return { status: 'skipped', detail: `skipped by ${optOut}` };
|
|
53
79
|
}
|
|
80
|
+
if (refuseOnLegacyLocalState(log, opts.findLegacyStateImpl)) {
|
|
81
|
+
return {
|
|
82
|
+
status: 'skipped',
|
|
83
|
+
detail: 'state from the previous product identity is still here — finish the cutover above, then run `skrr daemon install`',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
54
86
|
const interactive = opts.interactive ?? !(0, ssh_detect_1.isNonInteractive)();
|
|
55
87
|
if (!interactive) {
|
|
56
88
|
// A prompt with no terminal hangs forever, which is worse than the extra
|
|
@@ -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
|
@@ -83,7 +83,7 @@ interface AgentRow {
|
|
|
83
83
|
defaultForHarness?: string;
|
|
84
84
|
}
|
|
85
85
|
export interface FirstPartyHarnessAgentDeps {
|
|
86
|
-
/** Agents this user may run.
|
|
86
|
+
/** Agents this user may run. Paged and bounded; see `ROSTER_MAX`. */
|
|
87
87
|
listAgents?: (signal?: AbortSignal) => Promise<AgentRow[]>;
|
|
88
88
|
/** Direct read, for an id outside the bounded roster. `null` when unreadable. */
|
|
89
89
|
getAgent?: (id: string, signal?: AbortSignal) => Promise<AgentRow | null>;
|
|
@@ -158,4 +158,19 @@ export declare function readConfiguredAgentId(): string | undefined;
|
|
|
158
158
|
export declare function setConfiguredAgentId(ref: string, deps?: FirstPartyHarnessAgentDeps, options?: FirstPartyHarnessAgentOptions): Promise<FirstPartyHarnessAgent>;
|
|
159
159
|
/** Remove the setting. Returns what was there, so the caller can say so. */
|
|
160
160
|
export declare function clearConfiguredAgentId(): string | undefined;
|
|
161
|
+
/** One `/api/agents` response: a list page, or a bare array from an older server. */
|
|
162
|
+
export type AgentRosterPage = {
|
|
163
|
+
data?: AgentRow[];
|
|
164
|
+
agents?: AgentRow[];
|
|
165
|
+
has_more?: boolean;
|
|
166
|
+
after?: string | null;
|
|
167
|
+
} | AgentRow[];
|
|
168
|
+
/**
|
|
169
|
+
* Read roster pages until the server reports no more, up to `max` rows.
|
|
170
|
+
*
|
|
171
|
+
* A page that does not say `has_more` with a usable `after` cursor ends the walk,
|
|
172
|
+
* so an older server that ignores `cursor` (or returns a bare array) costs exactly
|
|
173
|
+
* the one request it always did.
|
|
174
|
+
*/
|
|
175
|
+
export declare function collectRosterPages(fetchPage: (cursor?: string) => Promise<AgentRosterPage>, max?: number): Promise<AgentRow[]>;
|
|
161
176
|
export {};
|
|
@@ -55,18 +55,26 @@ exports.resolveFirstPartyHarnessAgent = resolveFirstPartyHarnessAgent;
|
|
|
55
55
|
exports.readConfiguredAgentId = readConfiguredAgentId;
|
|
56
56
|
exports.setConfiguredAgentId = setConfiguredAgentId;
|
|
57
57
|
exports.clearConfiguredAgentId = clearConfiguredAgentId;
|
|
58
|
+
exports.collectRosterPages = collectRosterPages;
|
|
58
59
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
59
60
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
60
61
|
const api_fetch_1 = require("./api-fetch");
|
|
61
62
|
const config_1 = require("./config");
|
|
62
63
|
const first_party_harness_managed_1 = require("./first-party-harness-managed");
|
|
64
|
+
/** Page size for one `/api/agents` roster request. */
|
|
65
|
+
const ROSTER_PAGE_SIZE = 100;
|
|
63
66
|
/**
|
|
64
|
-
*
|
|
67
|
+
* The most agents the roster will read, across pages.
|
|
65
68
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
69
|
+
* The roster is not only the refusal's "did you mean" list. Resolution also finds
|
|
70
|
+
* the user's EXISTING default agent in it (`createDefault: false`) and matches a
|
|
71
|
+
* configured agent by name, so one fixed page answered "you have no default agent"
|
|
72
|
+
* for an account whose default was agent 198 of 210 (found by the end-to-end run
|
|
73
|
+
* on 2026-09-17). Pages are followed until the server says there are no more, up
|
|
74
|
+
* to this bound, which keeps a pathological account from turning one session
|
|
75
|
+
* start into an unbounded crawl.
|
|
68
76
|
*/
|
|
69
|
-
const
|
|
77
|
+
const ROSTER_MAX = 1000;
|
|
70
78
|
/** How many candidates a refusal prints before it summarizes the rest. */
|
|
71
79
|
const CANDIDATES_SHOWN = 12;
|
|
72
80
|
class FirstPartyHarnessAgentError extends Error {
|
|
@@ -220,7 +228,7 @@ async function resolveFirstPartyHarnessAgent(explicit, deps = {}, options = {})
|
|
|
220
228
|
const created = options.createDefault === false
|
|
221
229
|
? null
|
|
222
230
|
: await (deps.ensureDefaultAgent ?? ensureDefaultAgent)(
|
|
223
|
-
// Canonical: what the server stores
|
|
231
|
+
// Canonical: what the server stores, and the one spelling every server accepts.
|
|
224
232
|
auth_core_1.FIRST_PARTY_HARNESS.provider, options.signal);
|
|
225
233
|
if (created?.id) {
|
|
226
234
|
return {
|
|
@@ -330,11 +338,12 @@ function readRawConfigKey() {
|
|
|
330
338
|
* first real session read. Returns whether the file now holds it under the
|
|
331
339
|
* neutral key.
|
|
332
340
|
*
|
|
333
|
-
* Reading alone used to leave it where it was
|
|
334
|
-
*
|
|
335
|
-
*
|
|
341
|
+
* Reading alone used to leave it where it was. `legacyAgentConfigKeys()` keeps
|
|
342
|
+
* naming an old key after its spelling is retired (it reads the identity's
|
|
343
|
+
* ARTIFACT spellings, since the file predates the rename), but a key that is only
|
|
344
|
+
* ever read is one the next pruning of `formerSpellings` would strand, and a user
|
|
336
345
|
* who never ran `--set`, `--clear` or logout would silently lose their setting.
|
|
337
|
-
* Moving it
|
|
346
|
+
* Moving it on the first session read retires the old key long before that.
|
|
338
347
|
*
|
|
339
348
|
* Only the session path calls this — `skrr code doctor` reports and never
|
|
340
349
|
* writes. Best-effort: an unwritable config is left alone and still read.
|
|
@@ -368,14 +377,35 @@ function writeConfigKey(id) {
|
|
|
368
377
|
* `fields=summary` drops the prompt-sized fields; this needs an id and a name.
|
|
369
378
|
*/
|
|
370
379
|
async function listAgents(signal) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
380
|
+
return collectRosterPages((cursor) => (0, api_fetch_1.apiFetch)(`/api/agents?limit=${ROSTER_PAGE_SIZE}&fields=summary&requiredPermission=${data_provider_1.PermissionBits.EDIT}` +
|
|
381
|
+
(cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''), signal ? { signal } : {}));
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Read roster pages until the server reports no more, up to `max` rows.
|
|
385
|
+
*
|
|
386
|
+
* A page that does not say `has_more` with a usable `after` cursor ends the walk,
|
|
387
|
+
* so an older server that ignores `cursor` (or returns a bare array) costs exactly
|
|
388
|
+
* the one request it always did.
|
|
389
|
+
*/
|
|
390
|
+
async function collectRosterPages(fetchPage, max = ROSTER_MAX) {
|
|
391
|
+
const rows = [];
|
|
392
|
+
let cursor;
|
|
393
|
+
do {
|
|
394
|
+
const response = await fetchPage(cursor);
|
|
395
|
+
const page = Array.isArray(response)
|
|
396
|
+
? response
|
|
397
|
+
: Array.isArray(response?.data)
|
|
398
|
+
? response.data
|
|
399
|
+
: Array.isArray(response?.agents)
|
|
400
|
+
? response.agents
|
|
401
|
+
: [];
|
|
402
|
+
rows.push(...page);
|
|
403
|
+
const next = !Array.isArray(response) && response?.has_more === true && typeof response.after === 'string'
|
|
404
|
+
? response.after
|
|
405
|
+
: undefined;
|
|
406
|
+
cursor = next && next !== cursor && page.length > 0 ? next : undefined;
|
|
407
|
+
} while (cursor && rows.length < max);
|
|
408
|
+
return rows.slice(0, max);
|
|
379
409
|
}
|
|
380
410
|
/**
|
|
381
411
|
* The caller's own user id, memoized FOR THIS PROCESS ONLY.
|