@skrr-ai/cli 0.1.40 → 0.1.41
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 +9 -0
- package/dist/base-command.js +18 -3
- package/dist/commands/agent-shares/list-for.js +4 -11
- package/dist/commands/agent-shares/received.js +4 -11
- package/dist/commands/agent-shares/sent.js +4 -11
- package/dist/commands/agent-shares/with.js +3 -10
- package/dist/commands/agents/activity.js +1 -1
- package/dist/commands/agents/api-actions/add.js +6 -0
- package/dist/commands/agents/chat.js +6 -1
- package/dist/commands/agents/create.js +6 -1
- package/dist/commands/agents/manager/ensure.d.ts +18 -0
- package/dist/commands/agents/manager/ensure.js +56 -0
- package/dist/commands/agents/show.js +4 -0
- package/dist/commands/agents/tools/auth.d.ts +6 -0
- package/dist/commands/agents/tools/auth.js +27 -1
- package/dist/commands/agents/tools/index.js +11 -1
- package/dist/commands/agents/update.js +6 -1
- package/dist/commands/code/install.d.ts +4 -2
- package/dist/commands/code/install.js +12 -10
- package/dist/commands/convos/list.js +1 -3
- package/dist/commands/convos/show.js +4 -11
- package/dist/commands/daemon/index.js +3 -3
- package/dist/commands/daemon/install.js +4 -4
- package/dist/commands/daemon/login.js +1 -1
- package/dist/commands/daemon/logs.d.ts +3 -1
- package/dist/commands/daemon/logs.js +16 -1
- package/dist/commands/daemon/start.js +3 -3
- package/dist/commands/daemon/status.d.ts +9 -0
- package/dist/commands/daemon/status.js +22 -0
- package/dist/commands/dm/messages.js +1 -11
- package/dist/commands/followups/cancel.js +1 -1
- package/dist/commands/followups/resolve.js +1 -1
- package/dist/commands/followups/show.js +16 -0
- package/dist/commands/harnesses/forget.js +20 -3
- package/dist/commands/harnesses/install.js +18 -6
- package/dist/commands/harnesses/installers.d.ts +1 -1
- package/dist/commands/harnesses/installers.js +36 -12
- package/dist/commands/harnesses/list.d.ts +74 -2
- package/dist/commands/harnesses/list.js +231 -15
- package/dist/commands/harnesses/ping.d.ts +2 -0
- package/dist/commands/harnesses/ping.js +21 -0
- package/dist/commands/harnesses/show.d.ts +22 -0
- package/dist/commands/harnesses/show.js +116 -17
- package/dist/commands/harnesses/update.d.ts +8 -0
- package/dist/commands/harnesses/update.js +25 -1
- package/dist/commands/harnesses/usage.d.ts +2 -0
- package/dist/commands/harnesses/usage.js +7 -2
- package/dist/commands/instructions/status.js +13 -0
- package/dist/commands/instructions/update-target.d.ts +1 -0
- package/dist/commands/instructions/update-target.js +11 -3
- package/dist/commands/login.js +5 -5
- package/dist/commands/machines/list.js +8 -9
- package/dist/commands/machines/show.js +46 -17
- package/dist/commands/messages/show.js +3 -10
- package/dist/commands/shares/get.js +2 -9
- package/dist/commands/shares/list.js +1 -9
- package/dist/commands/shortcuts/show.js +4 -11
- package/dist/commands/skills/local.js +6 -0
- package/dist/commands/spaces/actions/run.js +3 -4
- package/dist/commands/spaces/briefs/get.js +2 -9
- package/dist/commands/spaces/briefs/list.js +1 -9
- package/dist/commands/spaces/conversations.js +1 -9
- package/dist/commands/spaces/heartbeats/list.js +1 -9
- package/dist/commands/spaces/heartbeats/status.js +2 -9
- package/dist/commands/tasks/activity.js +1 -10
- package/dist/commands/tasks/approvals/list.js +1 -9
- package/dist/commands/tasks/attachments/list.js +1 -9
- package/dist/commands/tasks/comments/list.js +1 -9
- package/dist/commands/tasks/dri-status.js +2 -9
- package/dist/commands/tasks/events/append.js +9 -7
- package/dist/commands/tasks/execution-timeline.js +1 -9
- package/dist/commands/tasks/monitor.js +1 -9
- package/dist/commands/tasks/result/submit.js +9 -0
- package/dist/commands/tasks/runs.js +1 -9
- package/dist/commands/tasks/show.js +5 -12
- package/dist/commands/token/list.d.ts +8 -0
- package/dist/commands/token/list.js +51 -6
- package/dist/help.js +8 -12
- package/dist/lib/agent-home-workspace.d.ts +19 -0
- package/dist/lib/agent-home-workspace.js +39 -0
- package/dist/lib/agent-resolver.js +2 -2
- package/dist/lib/agentic-stream.d.ts +22 -1
- package/dist/lib/agentic-stream.js +48 -7
- package/dist/lib/cli-installers.d.ts +84 -2
- package/dist/lib/cli-installers.js +146 -13
- package/dist/lib/command-miss.d.ts +22 -0
- package/dist/lib/command-miss.js +78 -0
- package/dist/lib/daemon-setup.js +2 -2
- package/dist/lib/daemon-target.d.ts +14 -0
- package/dist/lib/daemon-target.js +32 -0
- package/dist/lib/daemonBroker.js +1 -1
- package/dist/lib/daemonHandoff.js +1 -1
- package/dist/lib/exec-runtime-binary.js +2 -2
- package/dist/lib/followups.d.ts +10 -0
- package/dist/lib/followups.js +14 -1
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +21 -0
- package/dist/lib/harness-labels.d.ts +39 -0
- package/dist/lib/harness-labels.js +60 -0
- package/dist/lib/harnesses.d.ts +6 -0
- package/dist/lib/instruction-input.d.ts +13 -0
- package/dist/lib/instruction-input.js +20 -1
- package/dist/lib/local-skills.d.ts +17 -1
- package/dist/lib/local-skills.js +20 -2
- package/dist/lib/login.js +5 -5
- package/dist/lib/machines.d.ts +6 -0
- package/dist/lib/machines.js +68 -2
- package/dist/lib/runtime-tool-support.d.ts +34 -0
- package/dist/lib/runtime-tool-support.js +77 -0
- package/dist/lib/share-rows.d.ts +17 -0
- package/dist/lib/share-rows.js +35 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5029 -5007
- package/oclif.manifest.json +2689 -2584
- package/package.json +6 -3
|
@@ -51,7 +51,12 @@ function writeJsonLine(output, value) {
|
|
|
51
51
|
* The assistant text inside a `session:output` frame's envelopes.
|
|
52
52
|
*
|
|
53
53
|
* Envelope shape: `{ ev: { t: 'text', text: '…' } }`. Frames whose `ev.t` is
|
|
54
|
-
* anything else (tool calls, status) are not the answer and are skipped
|
|
54
|
+
* anything else (tool calls, status) are not the answer and are skipped, and so
|
|
55
|
+
* is text marked `thinking: true` — the model's reasoning, which the session
|
|
56
|
+
* protocol carries in the same `text` event. Folded in, a Devin turn asked for
|
|
57
|
+
* one word printed "I need to output only the word SECOND, … single
|
|
58
|
+
* word.SECOND" as its answer, and `run.result.text` kept it, because the
|
|
59
|
+
* stream then held the final result as a suffix and the longer text won.
|
|
55
60
|
*/
|
|
56
61
|
function envelopesText(event) {
|
|
57
62
|
const envelopes = event.envelopes;
|
|
@@ -59,9 +64,11 @@ function envelopesText(event) {
|
|
|
59
64
|
return '';
|
|
60
65
|
let out = '';
|
|
61
66
|
for (const envelope of envelopes) {
|
|
62
|
-
const ev = envelope
|
|
63
|
-
|
|
67
|
+
const ev = envelope
|
|
68
|
+
?.ev;
|
|
69
|
+
if (ev && ev.t === 'text' && ev.thinking !== true && typeof ev.text === 'string') {
|
|
64
70
|
out += ev.text;
|
|
71
|
+
}
|
|
65
72
|
}
|
|
66
73
|
return out;
|
|
67
74
|
}
|
|
@@ -306,6 +313,8 @@ class AgenticStreamClient {
|
|
|
306
313
|
/** Text already written to the terminal, for suffix-only rendering. */
|
|
307
314
|
renderedText = '';
|
|
308
315
|
idleTimer = null;
|
|
316
|
+
/** Releases a connect wait still in flight; `close()` calls it. */
|
|
317
|
+
abandonConnectWait = null;
|
|
309
318
|
reconciling = false;
|
|
310
319
|
responseMessageId;
|
|
311
320
|
lastEvent;
|
|
@@ -351,7 +360,13 @@ class AgenticStreamClient {
|
|
|
351
360
|
reconnectionDelayMax: 10_000,
|
|
352
361
|
});
|
|
353
362
|
this.installHandlers();
|
|
354
|
-
await this.waitForConnect();
|
|
363
|
+
await this.waitForConnect(this.options.attachTimeoutMs);
|
|
364
|
+
// A refused handshake asks the durable record, and the record can settle
|
|
365
|
+
// the run before any attach succeeds. `close()` then ends the wait, and
|
|
366
|
+
// there is nothing left to join: returning here is what lets the caller see
|
|
367
|
+
// the result instead of awaiting an attach nobody is still attempting.
|
|
368
|
+
if (this.settled || this.closed || !this.socket)
|
|
369
|
+
return result;
|
|
355
370
|
this.socket.emit('session:join', { sessionId: this.options.sessionId });
|
|
356
371
|
this.emit({ type: 'session.joined', sessionId: this.options.sessionId });
|
|
357
372
|
this.armIdleProbe();
|
|
@@ -365,6 +380,14 @@ class AgenticStreamClient {
|
|
|
365
380
|
* rather than giving up — a 200-second tool call is silence, not a failure —
|
|
366
381
|
* so the cost of being wrong is one cheap read, while the cost of not asking
|
|
367
382
|
* was an interactive session that never returned.
|
|
383
|
+
*
|
|
384
|
+
* The timer is deliberately NOT unref'd. It exists for the moment the socket
|
|
385
|
+
* stops holding the process open — a server-side disconnect, or a reconnect
|
|
386
|
+
* the server refuses, after which socket.io does not retry. Unref'd, it was
|
|
387
|
+
* the last thing pending at exactly that moment, so Node's event loop emptied
|
|
388
|
+
* and the CLI exited 0 with the run unsettled: `--no-stream --json` printed
|
|
389
|
+
* nothing at all for a turn the record said had failed (OSK-10061). A run
|
|
390
|
+
* that has not settled is a reason to stay alive; `close()` clears the timer.
|
|
368
391
|
*/
|
|
369
392
|
armIdleProbe() {
|
|
370
393
|
if (!this.options.reconcileTerminal)
|
|
@@ -374,7 +397,6 @@ class AgenticStreamClient {
|
|
|
374
397
|
if (this.idleTimer)
|
|
375
398
|
clearTimeout(this.idleTimer);
|
|
376
399
|
this.idleTimer = setTimeout(() => void this.probeTerminal('idle'), this.options.idleTerminalProbeMs ?? 20_000);
|
|
377
|
-
this.idleTimer.unref?.();
|
|
378
400
|
}
|
|
379
401
|
/**
|
|
380
402
|
* Settle from the durable record, if it has reached a terminal state.
|
|
@@ -438,6 +460,7 @@ class AgenticStreamClient {
|
|
|
438
460
|
clearTimeout(this.idleTimer);
|
|
439
461
|
this.idleTimer = null;
|
|
440
462
|
}
|
|
463
|
+
this.abandonConnectWait?.();
|
|
441
464
|
const socket = this.socket;
|
|
442
465
|
this.socket = null;
|
|
443
466
|
// Stop dispatch BEFORE tearing anything down. socket.io can deliver a
|
|
@@ -838,6 +861,11 @@ class AgenticStreamClient {
|
|
|
838
861
|
if (finalText && finalText !== this.accumulated && !accumulationIsSuperset) {
|
|
839
862
|
this.accumulated = finalText;
|
|
840
863
|
}
|
|
864
|
+
// The finished answer, as every other surface shows it: the web applies
|
|
865
|
+
// this same sanitizer, so a scheduling JSON block or leaked function-call
|
|
866
|
+
// XML the model wrote into its reply is removed here too instead of being
|
|
867
|
+
// printed as part of the agent's answer.
|
|
868
|
+
this.accumulated = (0, data_provider_1.sanitizeFinalText)(this.accumulated);
|
|
841
869
|
if (!this.options.quiet && !this.options.jsonl && this.accumulated) {
|
|
842
870
|
const output = this.options.output ?? process.stdout;
|
|
843
871
|
const { append, shown } = renderableDelta(this.renderedText, this.accumulated);
|
|
@@ -872,6 +900,10 @@ class AgenticStreamClient {
|
|
|
872
900
|
* Now errors are surfaced as events and retried until a bounded deadline, so
|
|
873
901
|
* only a sustained inability to attach gives up — and the caller turns that
|
|
874
902
|
* into "accepted but detached", never "failed".
|
|
903
|
+
*
|
|
904
|
+
* The deadline and retry timers hold the process open (see `armDeadline`), so
|
|
905
|
+
* `close()` is what releases them — and it ends the wait too, whether the
|
|
906
|
+
* caller gave up or the durable record settled the run first (OSK-10061).
|
|
875
907
|
*/
|
|
876
908
|
waitForConnect(timeoutMs = 30_000) {
|
|
877
909
|
if (this.socket?.connected)
|
|
@@ -889,7 +921,12 @@ class AgenticStreamClient {
|
|
|
889
921
|
reject(Object.assign(new Error(`Could not attach to the run stream within ${Math.round(ms / 1000)}s` +
|
|
890
922
|
`${lastError ? `: ${lastError.message}` : ''}`), { code: 'STREAM_ATTACH_TIMEOUT', lastError }));
|
|
891
923
|
}, ms);
|
|
892
|
-
|
|
924
|
+
// Deliberately NOT unref'd, nor is the retry timer below. A connect
|
|
925
|
+
// refused by middleware leaves no live socket, so these timers are the
|
|
926
|
+
// only thing keeping the process alive while it waits. Unref'd, Node's
|
|
927
|
+
// event loop drained and `agents chat` exited 0 right after printing
|
|
928
|
+
// "Retrying in 30s" — before the retry, without the answer the run had
|
|
929
|
+
// produced, and without the detach notice a script needs.
|
|
893
930
|
return handle;
|
|
894
931
|
}
|
|
895
932
|
const onConnect = () => {
|
|
@@ -933,7 +970,6 @@ class AgenticStreamClient {
|
|
|
933
970
|
// must not reject here and turn a pause into a failure.
|
|
934
971
|
}
|
|
935
972
|
}, jitteredRetryDelay(refusal.retryAfterMs));
|
|
936
|
-
retryTimer.unref?.();
|
|
937
973
|
};
|
|
938
974
|
const cleanup = () => {
|
|
939
975
|
clearTimeout(timer);
|
|
@@ -941,6 +977,11 @@ class AgenticStreamClient {
|
|
|
941
977
|
clearTimeout(retryTimer);
|
|
942
978
|
socket.off('connect', onConnect);
|
|
943
979
|
socket.off('connect_error', onError);
|
|
980
|
+
this.abandonConnectWait = null;
|
|
981
|
+
};
|
|
982
|
+
this.abandonConnectWait = () => {
|
|
983
|
+
cleanup();
|
|
984
|
+
resolve();
|
|
944
985
|
};
|
|
945
986
|
socket.once('connect', onConnect);
|
|
946
987
|
socket.on('connect_error', onError);
|
|
@@ -44,7 +44,7 @@ export interface CliInstallersResponse {
|
|
|
44
44
|
* Providers the platform knows about that this daemon did not offer.
|
|
45
45
|
*
|
|
46
46
|
* The inventory is whatever the CONNECTED daemon supports, and an older daemon
|
|
47
|
-
* legitimately supports fewer: `devin` arrived in
|
|
47
|
+
* legitimately supports fewer: `devin` arrived in the 0.8.45 daemon release, so a machine
|
|
48
48
|
* running 0.8.35 answers with six providers instead of seven. Observed here —
|
|
49
49
|
* the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
|
|
50
50
|
* disappeared from the install list between two page loads, with nothing on
|
|
@@ -56,8 +56,28 @@ export interface CliInstallersResponse {
|
|
|
56
56
|
* is already on this side of the wire. Naming the gap is what turns "Devin is
|
|
57
57
|
* gone" into "this daemon is older than Devin".
|
|
58
58
|
*/
|
|
59
|
+
/**
|
|
60
|
+
* The first STABLE daemon release (`daemon-v*` tag) whose
|
|
61
|
+
* MANAGED_CLI_PROVIDER_IDS included each provider. Release tags, not
|
|
62
|
+
* package.json at the commit that added it: the version is bumped before a
|
|
63
|
+
* release is cut, so the commit that added devin already read 0.8.43 while the
|
|
64
|
+
* 0.8.43 release did not contain it — this table said "added in 0.8.43" to a
|
|
65
|
+
* 0.8.43 user (OSK-9537 verifier). Derived with, per provider:
|
|
66
|
+
* for t in $(git tag -l 'daemon-v*' | sort -V); do
|
|
67
|
+
* git show "$t:daemon/src/cli-installers.ts" | grep -q "'devin'" && echo "$t" && break
|
|
68
|
+
* done
|
|
69
|
+
* Providers present since managed installers first shipped (0.8.1) are omitted.
|
|
70
|
+
*/
|
|
71
|
+
export declare const MANAGED_CLI_PROVIDER_SINCE: Readonly<Record<string, string>>;
|
|
72
|
+
/**
|
|
73
|
+
* "Not offered" with the release that added each provider, so a reader learns
|
|
74
|
+
* WHICH update fixes it rather than only that one might (OSK-9537).
|
|
75
|
+
*/
|
|
76
|
+
export declare function describeProvidersNotOffered(missing: readonly string[]): string;
|
|
59
77
|
export declare function providersNotOffered(offered: readonly ManagedCliProviderState[] | undefined, known: readonly string[]): string[];
|
|
60
|
-
export declare function listCliInstallers(daemonId: string, credential?: unknown
|
|
78
|
+
export declare function listCliInstallers(daemonId: string, credential?: unknown, opts?: {
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
}): Promise<CliInstallersResponse>;
|
|
61
81
|
/**
|
|
62
82
|
* The one-word readiness verdict for a provider, and whether it is good news.
|
|
63
83
|
*
|
|
@@ -74,10 +94,63 @@ export declare function readinessOf(provider: ManagedCliProviderState): {
|
|
|
74
94
|
ok: boolean;
|
|
75
95
|
};
|
|
76
96
|
/** A job the daemon runs to install one managed provider. */
|
|
97
|
+
/**
|
|
98
|
+
* Readiness per provider for one daemon, keyed by CANONICAL provider — or null
|
|
99
|
+
* when the daemon could not be asked (disconnected, older, refused).
|
|
100
|
+
*
|
|
101
|
+
* The harness row's `authStatus` is the DAEMON's standing with skrr, written
|
|
102
|
+
* across every row of a daemon at once, so `harnesses show`, `machines show`
|
|
103
|
+
* and `ping` answered "ok" per provider for a Devin its own CLI called "Not
|
|
104
|
+
* logged in" (OSK-9263, OSK-9265). This is the per-provider answer those
|
|
105
|
+
* surfaces print instead. Best-effort by design: a failure here must never
|
|
106
|
+
* fail the command that asked.
|
|
107
|
+
*/
|
|
108
|
+
export declare function readinessByProvider(daemonId: string, credential?: unknown, opts?: {
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}): Promise<Map<string, ReturnType<typeof readinessOf>> | null>;
|
|
111
|
+
/** The canonical provider a harness row names, for looking it up in that map. */
|
|
112
|
+
export declare function readinessKey(provider: string | null | undefined): string;
|
|
113
|
+
export type ProviderReadiness = ReturnType<typeof readinessOf>;
|
|
114
|
+
/** The fields of a harness row that decide whether, and whom, to ask. */
|
|
115
|
+
export interface ReadinessRow {
|
|
116
|
+
daemonId?: string | null;
|
|
117
|
+
provider?: string | null;
|
|
118
|
+
status?: string | null;
|
|
119
|
+
superseded?: boolean;
|
|
120
|
+
}
|
|
121
|
+
/** How long one daemon may take to answer before a list prints `-` for it. */
|
|
122
|
+
export declare const ROW_READINESS_TIMEOUT_MS = 5000;
|
|
123
|
+
/**
|
|
124
|
+
* Readiness for many harness rows at once: ONE installer read per connected
|
|
125
|
+
* daemon, all in parallel, however many rows each daemon has.
|
|
126
|
+
*
|
|
127
|
+
* Only a row whose daemon can answer is asked for — online and not superseded;
|
|
128
|
+
* an offline or replaced registration has nobody on the other end. Each read is
|
|
129
|
+
* bounded, because a list must not hang on one slow machine; a daemon that does
|
|
130
|
+
* not answer in time reads as unknown (`undefined`), never as `ready`.
|
|
131
|
+
*
|
|
132
|
+
* `asked` and `answered` let a caller tell "this daemon does not report it"
|
|
133
|
+
* from "nothing could be asked", which call for different next steps.
|
|
134
|
+
*/
|
|
135
|
+
export declare function readinessForRows(rows: readonly ReadinessRow[], credential?: unknown, opts?: {
|
|
136
|
+
timeoutMs?: number;
|
|
137
|
+
}): Promise<{
|
|
138
|
+
lookup: (row: ReadinessRow) => ProviderReadiness | undefined;
|
|
139
|
+
/** Whether this row's daemon answered at all — so a `-` can say why. */
|
|
140
|
+
daemonAnswered: (row: ReadinessRow) => boolean;
|
|
141
|
+
asked: number;
|
|
142
|
+
answered: number;
|
|
143
|
+
}>;
|
|
77
144
|
export interface ManagedCliInstallJob {
|
|
78
145
|
id: string;
|
|
79
146
|
status: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
80
147
|
error?: string;
|
|
148
|
+
/**
|
|
149
|
+
* The daemon's job log: the LAST lines only (a sliding window, 200 lines in
|
|
150
|
+
* `daemon/src/cli-installers.ts`). This is the field the daemon sends.
|
|
151
|
+
*/
|
|
152
|
+
logTail?: string[];
|
|
153
|
+
/** Never sent by any daemon; kept so an older caller's shape still types. */
|
|
81
154
|
log?: string[];
|
|
82
155
|
}
|
|
83
156
|
export interface StartInstallResponse {
|
|
@@ -92,6 +165,15 @@ export declare function startManagedCliInstall(daemonId: string, provider: strin
|
|
|
92
165
|
export declare function readManagedCliInstallJob(daemonId: string, jobId: string, credential?: unknown): Promise<{
|
|
93
166
|
job?: ManagedCliInstallJob;
|
|
94
167
|
}>;
|
|
168
|
+
/**
|
|
169
|
+
* The lines of a job's log tail not yet printed.
|
|
170
|
+
*
|
|
171
|
+
* The tail is a SLIDING window: once an installer writes more than the daemon
|
|
172
|
+
* keeps, earlier lines fall off the front, so an index into it skips or repeats
|
|
173
|
+
* lines. Instead, find the longest run at the start of the new tail that matches
|
|
174
|
+
* the end of what was already printed, and print only what follows it.
|
|
175
|
+
*/
|
|
176
|
+
export declare function unseenLogLines(printed: readonly string[], tail: readonly string[]): string[];
|
|
95
177
|
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
96
178
|
export declare const INSTALL_MAX_WAIT_MS: number;
|
|
97
179
|
export declare const INSTALL_POLL_INTERVAL_MS = 2000;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = void 0;
|
|
3
|
+
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = exports.ROW_READINESS_TIMEOUT_MS = exports.MANAGED_CLI_PROVIDER_SINCE = void 0;
|
|
4
|
+
exports.describeProvidersNotOffered = describeProvidersNotOffered;
|
|
4
5
|
exports.providersNotOffered = providersNotOffered;
|
|
5
6
|
exports.listCliInstallers = listCliInstallers;
|
|
6
7
|
exports.readinessOf = readinessOf;
|
|
8
|
+
exports.readinessByProvider = readinessByProvider;
|
|
9
|
+
exports.readinessKey = readinessKey;
|
|
10
|
+
exports.readinessForRows = readinessForRows;
|
|
7
11
|
exports.startManagedCliInstall = startManagedCliInstall;
|
|
8
12
|
exports.readManagedCliInstallJob = readManagedCliInstallJob;
|
|
13
|
+
exports.unseenLogLines = unseenLogLines;
|
|
9
14
|
exports.followManagedCliInstallJob = followManagedCliInstallJob;
|
|
10
15
|
/**
|
|
11
16
|
* The daemon's managed-CLI installer inventory, as the CLI reads it.
|
|
@@ -21,19 +26,24 @@ exports.followManagedCliInstallJob = followManagedCliInstallJob;
|
|
|
21
26
|
* The CLI called the route from exactly one place (`commands/code/install.ts`),
|
|
22
27
|
* for one provider, to read one field, and showed the user none of it. So from
|
|
23
28
|
* the CLI there was no way to learn that a harness listed as `online` had no
|
|
24
|
-
* binary at all — which is the state this account's
|
|
29
|
+
* binary at all — which is the state this account's first-party harness row was in.
|
|
25
30
|
*
|
|
26
31
|
* The shape lives here rather than beside a command because two commands read
|
|
27
32
|
* it now. Root `CLAUDE.md`: a second declaration is how a surface silently
|
|
28
33
|
* drifts from the server that produced it.
|
|
29
34
|
*/
|
|
35
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
30
36
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
31
37
|
const api_fetch_1 = require("./api-fetch");
|
|
38
|
+
/** A provider in its canonical spelling, or '' for a missing one. */
|
|
39
|
+
function canonical(value) {
|
|
40
|
+
return typeof value === 'string' ? String((0, data_provider_1.canonicalHarnessProvider)(value) ?? value) : '';
|
|
41
|
+
}
|
|
32
42
|
/**
|
|
33
43
|
* Providers the platform knows about that this daemon did not offer.
|
|
34
44
|
*
|
|
35
45
|
* The inventory is whatever the CONNECTED daemon supports, and an older daemon
|
|
36
|
-
* legitimately supports fewer: `devin` arrived in
|
|
46
|
+
* legitimately supports fewer: `devin` arrived in the 0.8.45 daemon release, so a machine
|
|
37
47
|
* running 0.8.35 answers with six providers instead of seven. Observed here —
|
|
38
48
|
* the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
|
|
39
49
|
* disappeared from the install list between two page loads, with nothing on
|
|
@@ -45,19 +55,47 @@ const api_fetch_1 = require("./api-fetch");
|
|
|
45
55
|
* is already on this side of the wire. Naming the gap is what turns "Devin is
|
|
46
56
|
* gone" into "this daemon is older than Devin".
|
|
47
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* The first STABLE daemon release (`daemon-v*` tag) whose
|
|
60
|
+
* MANAGED_CLI_PROVIDER_IDS included each provider. Release tags, not
|
|
61
|
+
* package.json at the commit that added it: the version is bumped before a
|
|
62
|
+
* release is cut, so the commit that added devin already read 0.8.43 while the
|
|
63
|
+
* 0.8.43 release did not contain it — this table said "added in 0.8.43" to a
|
|
64
|
+
* 0.8.43 user (OSK-9537 verifier). Derived with, per provider:
|
|
65
|
+
* for t in $(git tag -l 'daemon-v*' | sort -V); do
|
|
66
|
+
* git show "$t:daemon/src/cli-installers.ts" | grep -q "'devin'" && echo "$t" && break
|
|
67
|
+
* done
|
|
68
|
+
* Providers present since managed installers first shipped (0.8.1) are omitted.
|
|
69
|
+
*/
|
|
70
|
+
exports.MANAGED_CLI_PROVIDER_SINCE = Object.freeze({
|
|
71
|
+
// First offered under its older spelling; the canonical key covers both.
|
|
72
|
+
[auth_core_1.FIRST_PARTY_HARNESS.provider]: '0.8.2',
|
|
73
|
+
devin: '0.8.45',
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* "Not offered" with the release that added each provider, so a reader learns
|
|
77
|
+
* WHICH update fixes it rather than only that one might (OSK-9537).
|
|
78
|
+
*/
|
|
79
|
+
function describeProvidersNotOffered(missing) {
|
|
80
|
+
return missing
|
|
81
|
+
.map((provider) => {
|
|
82
|
+
const since = exports.MANAGED_CLI_PROVIDER_SINCE[canonical(provider)];
|
|
83
|
+
return since ? `${provider} (added in daemon ${since})` : provider;
|
|
84
|
+
})
|
|
85
|
+
.join(', ');
|
|
86
|
+
}
|
|
48
87
|
function providersNotOffered(offered, known) {
|
|
49
88
|
// Canonicalize BOTH sides. An older daemon answers under the spelling it was
|
|
50
|
-
// built with — the 0.8.35 build reports the first-party harness
|
|
51
|
-
//
|
|
89
|
+
// built with — the 0.8.35 build reports the first-party harness under its
|
|
90
|
+
// older spelling, so a raw comparison against the current one reported it missing on
|
|
52
91
|
// a machine where it is installed and working. Caught by running this against
|
|
53
92
|
// the very daemon the finding was about: the first version of this helper
|
|
54
|
-
// said "Not offered: devin,
|
|
55
|
-
const canonical = (value) => typeof value === 'string' ? String((0, data_provider_1.canonicalHarnessProvider)(value) ?? value) : '';
|
|
93
|
+
// said "Not offered: devin, <first-party harness>" when only `devin` was true.
|
|
56
94
|
const seen = new Set((offered ?? []).map((provider) => canonical(provider.provider)).filter(Boolean));
|
|
57
95
|
return known.filter((provider) => !seen.has(canonical(provider)));
|
|
58
96
|
}
|
|
59
|
-
async function listCliInstallers(daemonId, credential) {
|
|
60
|
-
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: credential });
|
|
97
|
+
async function listCliInstallers(daemonId, credential, opts = {}) {
|
|
98
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: credential, ...(opts.signal ? { signal: opts.signal } : {}) });
|
|
61
99
|
}
|
|
62
100
|
/**
|
|
63
101
|
* The one-word readiness verdict for a provider, and whether it is good news.
|
|
@@ -95,18 +133,110 @@ function readinessOf(provider) {
|
|
|
95
133
|
if (!auth) {
|
|
96
134
|
return {
|
|
97
135
|
label: 'unknown',
|
|
98
|
-
detail:
|
|
136
|
+
detail: (0, data_provider_1.managedCliReadinessUnknownMessage)(),
|
|
99
137
|
ok: false,
|
|
100
138
|
};
|
|
101
139
|
}
|
|
102
140
|
return { label: auth.status, detail: auth.message, ok: auth.status === 'ready' };
|
|
103
141
|
}
|
|
142
|
+
/** A job the daemon runs to install one managed provider. */
|
|
143
|
+
/**
|
|
144
|
+
* Readiness per provider for one daemon, keyed by CANONICAL provider — or null
|
|
145
|
+
* when the daemon could not be asked (disconnected, older, refused).
|
|
146
|
+
*
|
|
147
|
+
* The harness row's `authStatus` is the DAEMON's standing with skrr, written
|
|
148
|
+
* across every row of a daemon at once, so `harnesses show`, `machines show`
|
|
149
|
+
* and `ping` answered "ok" per provider for a Devin its own CLI called "Not
|
|
150
|
+
* logged in" (OSK-9263, OSK-9265). This is the per-provider answer those
|
|
151
|
+
* surfaces print instead. Best-effort by design: a failure here must never
|
|
152
|
+
* fail the command that asked.
|
|
153
|
+
*/
|
|
154
|
+
async function readinessByProvider(daemonId, credential, opts = {}) {
|
|
155
|
+
let response;
|
|
156
|
+
try {
|
|
157
|
+
response = await listCliInstallers(daemonId, credential, opts);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const out = new Map();
|
|
163
|
+
for (const provider of response.providers ?? []) {
|
|
164
|
+
const key = canonical(provider.provider);
|
|
165
|
+
if (key)
|
|
166
|
+
out.set(key, readinessOf(provider));
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
/** The canonical provider a harness row names, for looking it up in that map. */
|
|
171
|
+
function readinessKey(provider) {
|
|
172
|
+
return canonical(provider);
|
|
173
|
+
}
|
|
174
|
+
/** How long one daemon may take to answer before a list prints `-` for it. */
|
|
175
|
+
exports.ROW_READINESS_TIMEOUT_MS = 5_000;
|
|
176
|
+
/**
|
|
177
|
+
* Readiness for many harness rows at once: ONE installer read per connected
|
|
178
|
+
* daemon, all in parallel, however many rows each daemon has.
|
|
179
|
+
*
|
|
180
|
+
* Only a row whose daemon can answer is asked for — online and not superseded;
|
|
181
|
+
* an offline or replaced registration has nobody on the other end. Each read is
|
|
182
|
+
* bounded, because a list must not hang on one slow machine; a daemon that does
|
|
183
|
+
* not answer in time reads as unknown (`undefined`), never as `ready`.
|
|
184
|
+
*
|
|
185
|
+
* `asked` and `answered` let a caller tell "this daemon does not report it"
|
|
186
|
+
* from "nothing could be asked", which call for different next steps.
|
|
187
|
+
*/
|
|
188
|
+
async function readinessForRows(rows, credential, opts = {}) {
|
|
189
|
+
const daemonIds = [
|
|
190
|
+
...new Set(rows
|
|
191
|
+
.filter((row) => row.status === 'online' && !row.superseded && row.daemonId)
|
|
192
|
+
.map((row) => row.daemonId)),
|
|
193
|
+
];
|
|
194
|
+
const timeoutMs = opts.timeoutMs ?? exports.ROW_READINESS_TIMEOUT_MS;
|
|
195
|
+
const answers = await Promise.all(daemonIds.map(async (daemonId) => [
|
|
196
|
+
daemonId,
|
|
197
|
+
await readinessByProvider(daemonId, credential, {
|
|
198
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
199
|
+
}),
|
|
200
|
+
]));
|
|
201
|
+
const byDaemon = new Map(answers);
|
|
202
|
+
return {
|
|
203
|
+
lookup: (row) => row.status === 'online' && !row.superseded && row.daemonId
|
|
204
|
+
? byDaemon.get(row.daemonId)?.get(readinessKey(row.provider))
|
|
205
|
+
: undefined,
|
|
206
|
+
daemonAnswered: (row) => Boolean(row.daemonId && byDaemon.get(row.daemonId)),
|
|
207
|
+
asked: daemonIds.length,
|
|
208
|
+
answered: answers.filter(([, answer]) => answer !== null).length,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
104
211
|
async function startManagedCliInstall(daemonId, provider, credential) {
|
|
105
212
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/install`, { method: 'POST', body: {}, credential: credential });
|
|
106
213
|
}
|
|
107
214
|
async function readManagedCliInstallJob(daemonId, jobId, credential) {
|
|
108
215
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/jobs/${encodeURIComponent(jobId)}`, { credential: credential });
|
|
109
216
|
}
|
|
217
|
+
const MAX_TRACKED_LOG_LINES = 400;
|
|
218
|
+
/**
|
|
219
|
+
* The lines of a job's log tail not yet printed.
|
|
220
|
+
*
|
|
221
|
+
* The tail is a SLIDING window: once an installer writes more than the daemon
|
|
222
|
+
* keeps, earlier lines fall off the front, so an index into it skips or repeats
|
|
223
|
+
* lines. Instead, find the longest run at the start of the new tail that matches
|
|
224
|
+
* the end of what was already printed, and print only what follows it.
|
|
225
|
+
*/
|
|
226
|
+
function unseenLogLines(printed, tail) {
|
|
227
|
+
for (let overlap = Math.min(printed.length, tail.length); overlap > 0; overlap -= 1) {
|
|
228
|
+
let matches = true;
|
|
229
|
+
for (let i = 0; i < overlap; i += 1) {
|
|
230
|
+
if (printed[printed.length - overlap + i] !== tail[i]) {
|
|
231
|
+
matches = false;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (matches)
|
|
236
|
+
return tail.slice(overlap);
|
|
237
|
+
}
|
|
238
|
+
return [...tail];
|
|
239
|
+
}
|
|
110
240
|
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
111
241
|
exports.INSTALL_MAX_WAIT_MS = 10 * 60_000;
|
|
112
242
|
exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
@@ -125,16 +255,19 @@ exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
|
125
255
|
*/
|
|
126
256
|
async function followManagedCliInstallJob(input) {
|
|
127
257
|
const deadline = Date.now() + (input.maxWaitMs ?? exports.INSTALL_MAX_WAIT_MS);
|
|
128
|
-
let printed =
|
|
258
|
+
let printed = [];
|
|
129
259
|
for (;;) {
|
|
130
260
|
const res = await readManagedCliInstallJob(input.daemonId, input.jobId, input.credential);
|
|
131
261
|
const job = res.job;
|
|
132
262
|
if (!job)
|
|
133
263
|
return { kind: 'missing', jobId: input.jobId };
|
|
134
264
|
if (input.onLogLine) {
|
|
135
|
-
|
|
265
|
+
// `logTail`, the field the daemon sends — this read `log`, which no daemon
|
|
266
|
+
// has ever sent, so every install printed nothing until it ended (OSK-10103).
|
|
267
|
+
const fresh = unseenLogLines(printed, job.logTail ?? job.log ?? []);
|
|
268
|
+
for (const line of fresh)
|
|
136
269
|
input.onLogLine(line);
|
|
137
|
-
printed =
|
|
270
|
+
printed = [...printed, ...fresh].slice(-MAX_TRACKED_LOG_LINES);
|
|
138
271
|
}
|
|
139
272
|
if (job.status === 'succeeded' || job.status === 'failed')
|
|
140
273
|
return { kind: 'settled', job };
|
|
@@ -23,6 +23,8 @@ export type CommandLookup = {
|
|
|
23
23
|
bin: string;
|
|
24
24
|
findCommand: (id: string) => {
|
|
25
25
|
flags?: Record<string, unknown>;
|
|
26
|
+
args?: Record<string, unknown>;
|
|
27
|
+
strict?: boolean;
|
|
26
28
|
} | undefined;
|
|
27
29
|
commandIDs: string[];
|
|
28
30
|
topicNames: string[];
|
|
@@ -34,6 +36,26 @@ export declare function editDistance(a: string, b: string): number;
|
|
|
34
36
|
* only a verdict.
|
|
35
37
|
*/
|
|
36
38
|
export declare function nearestCommands(id: string, all: string[], limit?: number): string[];
|
|
39
|
+
/**
|
|
40
|
+
* The command id a `--help` request is about, resolved the way oclif resolves
|
|
41
|
+
* the same words when they are RUN.
|
|
42
|
+
*
|
|
43
|
+
* The help override used to join every leading word into the id, so
|
|
44
|
+
* `skrr harnesses install devin --help` asked for `harnesses:install:devin`,
|
|
45
|
+
* found no such command, and reported that `harnesses install` "does not take
|
|
46
|
+
* the positional argument `devin`" — the positional its own help lists
|
|
47
|
+
* (OSK-10099). Running the same words works, because oclif stops collecting id
|
|
48
|
+
* words at the first resolved command that takes arguments: from there on a
|
|
49
|
+
* word is an argument, not part of the name. Adding `--help` to a command you
|
|
50
|
+
* are about to run is the commonest way to check it, so the two must agree.
|
|
51
|
+
*
|
|
52
|
+
* Mirrors `collateSpacedCmdIDFromArgs` in `@oclif/core/lib/help/util` (not a
|
|
53
|
+
* public export): a word extends the id while the longer id is a command or a
|
|
54
|
+
* prefix of one; otherwise it is appended only when the id so far is not a
|
|
55
|
+
* command that accepts arguments, so a genuinely wrong word under a command
|
|
56
|
+
* that takes none (`daemon reload`) still reaches the miss message.
|
|
57
|
+
*/
|
|
58
|
+
export declare function helpSubjectId(tokens: readonly string[], config: CommandLookup): string;
|
|
37
59
|
/**
|
|
38
60
|
* The message for a colon-joined id that did not resolve.
|
|
39
61
|
*
|
package/dist/lib/command-miss.js
CHANGED
|
@@ -22,10 +22,44 @@
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.editDistance = editDistance;
|
|
24
24
|
exports.nearestCommands = nearestCommands;
|
|
25
|
+
exports.helpSubjectId = helpSubjectId;
|
|
25
26
|
exports.describeCommandMiss = describeCommandMiss;
|
|
26
27
|
exports.lookupFromConfig = lookupFromConfig;
|
|
27
28
|
/** A uuid-ish token, which is what a stray space id looks like. */
|
|
28
29
|
const LOOKS_LIKE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
30
|
+
const DAEMON_READINESS = [
|
|
31
|
+
{
|
|
32
|
+
id: 'harnesses:installers',
|
|
33
|
+
usage: 'harnesses installers --daemon <id>',
|
|
34
|
+
answers: 'whether each coding-agent CLI on that machine is installed, and signed in where the daemon can tell',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'daemon:status',
|
|
38
|
+
usage: 'daemon status',
|
|
39
|
+
answers: 'whether the daemon itself is running and connected',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
const ANSWERED_ELSEWHERE = {
|
|
43
|
+
daemon: {
|
|
44
|
+
health: DAEMON_READINESS,
|
|
45
|
+
readiness: DAEMON_READINESS,
|
|
46
|
+
ready: DAEMON_READINESS,
|
|
47
|
+
doctor: DAEMON_READINESS,
|
|
48
|
+
check: DAEMON_READINESS,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
// `skrr daemons <sub>` is the same topic.
|
|
52
|
+
const ANSWERED_ELSEWHERE_BY_COMMAND = { ...ANSWERED_ELSEWHERE, daemons: ANSWERED_ELSEWHERE.daemon };
|
|
53
|
+
function describeAnsweredElsewhere(config, resolved, extras) {
|
|
54
|
+
if (extras.length !== 1)
|
|
55
|
+
return null;
|
|
56
|
+
const targets = (ANSWERED_ELSEWHERE_BY_COMMAND[resolved]?.[extras[0].toLowerCase()] ?? []).filter((target) => config.findCommand(target.id) !== undefined);
|
|
57
|
+
if (targets.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
const spoken = `${config.bin} ${resolved.split(':').join(' ')}`;
|
|
60
|
+
return (`\`${spoken}\` has no \`${extras[0]}\`. Depending on what you want to know:\n` +
|
|
61
|
+
targets.map((t) => ` \`${config.bin} ${t.usage}\` — ${t.answers}`).join('\n'));
|
|
62
|
+
}
|
|
29
63
|
/** Cheap edit distance, bounded — this runs only on a miss. */
|
|
30
64
|
function editDistance(a, b) {
|
|
31
65
|
const rows = a.length + 1;
|
|
@@ -60,6 +94,47 @@ function nearestCommands(id, all, limit = 3) {
|
|
|
60
94
|
.slice(0, limit)
|
|
61
95
|
.map(({ candidate }) => candidate));
|
|
62
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The command id a `--help` request is about, resolved the way oclif resolves
|
|
99
|
+
* the same words when they are RUN.
|
|
100
|
+
*
|
|
101
|
+
* The help override used to join every leading word into the id, so
|
|
102
|
+
* `skrr harnesses install devin --help` asked for `harnesses:install:devin`,
|
|
103
|
+
* found no such command, and reported that `harnesses install` "does not take
|
|
104
|
+
* the positional argument `devin`" — the positional its own help lists
|
|
105
|
+
* (OSK-10099). Running the same words works, because oclif stops collecting id
|
|
106
|
+
* words at the first resolved command that takes arguments: from there on a
|
|
107
|
+
* word is an argument, not part of the name. Adding `--help` to a command you
|
|
108
|
+
* are about to run is the commonest way to check it, so the two must agree.
|
|
109
|
+
*
|
|
110
|
+
* Mirrors `collateSpacedCmdIDFromArgs` in `@oclif/core/lib/help/util` (not a
|
|
111
|
+
* public export): a word extends the id while the longer id is a command or a
|
|
112
|
+
* prefix of one; otherwise it is appended only when the id so far is not a
|
|
113
|
+
* command that accepts arguments, so a genuinely wrong word under a command
|
|
114
|
+
* that takes none (`daemon reload`) still reaches the miss message.
|
|
115
|
+
*/
|
|
116
|
+
function helpSubjectId(tokens, config) {
|
|
117
|
+
const usable = new Set();
|
|
118
|
+
for (const id of [...config.commandIDs, ...config.topicNames]) {
|
|
119
|
+
const parts = id.split(':');
|
|
120
|
+
for (let i = 1; i <= parts.length; i += 1)
|
|
121
|
+
usable.add(parts.slice(0, i).join(':'));
|
|
122
|
+
}
|
|
123
|
+
const taken = [];
|
|
124
|
+
for (const token of tokens) {
|
|
125
|
+
if (token.startsWith('-') || token.includes('='))
|
|
126
|
+
break;
|
|
127
|
+
if (usable.has([...taken, token].join(':'))) {
|
|
128
|
+
taken.push(token);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const current = taken.length > 0 ? config.findCommand(taken.join(':')) : undefined;
|
|
132
|
+
if (current && (current.strict === false || Object.keys(current.args ?? {}).length > 0))
|
|
133
|
+
break;
|
|
134
|
+
taken.push(token);
|
|
135
|
+
}
|
|
136
|
+
return taken.join(':');
|
|
137
|
+
}
|
|
63
138
|
/**
|
|
64
139
|
* The message for a colon-joined id that did not resolve.
|
|
65
140
|
*
|
|
@@ -81,6 +156,9 @@ function describeCommandMiss(config, id) {
|
|
|
81
156
|
if (!found)
|
|
82
157
|
continue;
|
|
83
158
|
const extras = parts.slice(take);
|
|
159
|
+
const redirect = describeAnsweredElsewhere(config, parts.slice(0, take).join(':'), extras);
|
|
160
|
+
if (redirect)
|
|
161
|
+
return redirect;
|
|
84
162
|
const spoken = `${config.bin} ${parts.slice(0, take).join(' ')}`;
|
|
85
163
|
// Suggest the flag only when the command actually has it, so the hint can
|
|
86
164
|
// never point at something that does not exist.
|
package/dist/lib/daemon-setup.js
CHANGED
|
@@ -62,7 +62,7 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
const confirmImpl = opts.confirmImpl ?? prompt_1.confirm;
|
|
64
64
|
log('');
|
|
65
|
-
log(' This
|
|
65
|
+
log(' This computer has no skrr background service. It is what lets your agents work here —');
|
|
66
66
|
log(' running commands, editing files, and driving a browser on this machine.');
|
|
67
67
|
// This creates a persistent service with broad local capabilities, so a
|
|
68
68
|
// bare Enter must visibly and behaviorally decline it. `confirm` names its
|
|
@@ -100,7 +100,7 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
100
100
|
if (err.message === 'runtime-binary-not-installed') {
|
|
101
101
|
return {
|
|
102
102
|
status: 'failed',
|
|
103
|
-
detail: 'the
|
|
103
|
+
detail: 'the background service vanished between fetching and installing it',
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
return { status: 'failed', detail: err.message };
|
|
@@ -99,5 +99,19 @@ export interface ResolveDaemonTargetInput {
|
|
|
99
99
|
listActiveDaemons: () => Promise<ActiveDaemonRow[]>;
|
|
100
100
|
}
|
|
101
101
|
export declare function resolveDaemonTarget(input: ResolveDaemonTargetInput): Promise<DaemonTarget>;
|
|
102
|
+
/**
|
|
103
|
+
* Daemons that are CONNECTED right now, under the id the daemon-control API
|
|
104
|
+
* takes (`daemon_…`, `dedicated-runtime-…`).
|
|
105
|
+
*
|
|
106
|
+
* `listActiveDaemons` returns Daemon RESOURCE rows: "active" means not revoked,
|
|
107
|
+
* and `id` is the resource's own id. The installer routes relay an RPC to a
|
|
108
|
+
* connected daemon by its connection id, so that list could not be used to pick
|
|
109
|
+
* one: its refusal counted 50 "active daemons" — most of them sandboxes gone for
|
|
110
|
+
* weeks — and suggested values like `d_69eb2d1e...` that `--daemon` then failed
|
|
111
|
+
* to find, and a single "active" daemon would have been auto-picked by an id no
|
|
112
|
+
* route accepts (OSK-9993). Online harness rows carry exactly the ids and the
|
|
113
|
+
* liveness needed, one row per (daemon, provider).
|
|
114
|
+
*/
|
|
115
|
+
export declare function listConnectedDaemons(credential: ResolvedCredential): Promise<ActiveDaemonRow[]>;
|
|
102
116
|
/** The same list `skrr list-daemons` prints, so a refusal can name values that work. */
|
|
103
117
|
export declare function listActiveDaemons(credential: ResolvedCredential): Promise<ActiveDaemonRow[]>;
|