@skrr-ai/cli 0.1.40 → 0.1.42
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 +15 -14
- 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/remind.js +4 -0
- 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.d.ts +18 -0
- package/dist/commands/harnesses/install.js +146 -25
- 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/import-as-actions.js +6 -15
- 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/complete.js +13 -0
- 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 +14 -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 +124 -2
- package/dist/lib/cli-installers.js +196 -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 +12 -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 +44 -1
- package/dist/lib/local-skills.js +58 -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/lib/task-closure.d.ts +13 -0
- package/dist/lib/task-closure.js +17 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5066 -5033
- package/oclif.manifest.json +33472 -33360
- package/package.json +6 -3
|
@@ -11,11 +11,19 @@ import { BaseCommand } from '../../base-command';
|
|
|
11
11
|
*
|
|
12
12
|
* `GET /api/auth/sessions/refresh` has served exactly this list all along; it
|
|
13
13
|
* was simply not reachable from the CLI.
|
|
14
|
+
*
|
|
15
|
+
* The server returns at most `--limit` rows and says whether it left any out.
|
|
16
|
+
* It used to stop at 100 without saying so, and the session somebody went
|
|
17
|
+
* looking for — an old daemon's, still holding the login a purge refusal named
|
|
18
|
+
* — was exactly the one past the cut (OSK-9980). So a partial list says it is
|
|
19
|
+
* partial, and `--kind daemon` is the short way to the row that matters.
|
|
14
20
|
*/
|
|
15
21
|
export default class TokenList extends BaseCommand {
|
|
16
22
|
static description: string;
|
|
17
23
|
static examples: string[];
|
|
18
24
|
static flags: {
|
|
25
|
+
kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
|
+
limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
27
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
28
|
};
|
|
21
29
|
run(): Promise<void>;
|
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const base_command_1 = require("../../base-command");
|
|
5
5
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
6
6
|
const format_1 = require("../../lib/format");
|
|
7
|
+
const SESSION_KINDS = ['web', 'daemon', 'cli', 'ci'];
|
|
7
8
|
/**
|
|
8
9
|
* `skrr token list`
|
|
9
10
|
*
|
|
@@ -16,20 +17,44 @@ const format_1 = require("../../lib/format");
|
|
|
16
17
|
*
|
|
17
18
|
* `GET /api/auth/sessions/refresh` has served exactly this list all along; it
|
|
18
19
|
* was simply not reachable from the CLI.
|
|
20
|
+
*
|
|
21
|
+
* The server returns at most `--limit` rows and says whether it left any out.
|
|
22
|
+
* It used to stop at 100 without saying so, and the session somebody went
|
|
23
|
+
* looking for — an old daemon's, still holding the login a purge refusal named
|
|
24
|
+
* — was exactly the one past the cut (OSK-9980). So a partial list says it is
|
|
25
|
+
* partial, and `--kind daemon` is the short way to the row that matters.
|
|
19
26
|
*/
|
|
20
27
|
class TokenList extends base_command_1.BaseCommand {
|
|
21
28
|
static description = 'List the active sessions a minted token belongs to — the other half of `token mint`';
|
|
22
29
|
static examples = [
|
|
23
30
|
'<%= config.bin %> token list',
|
|
24
|
-
'<%= config.bin %> token list --
|
|
31
|
+
'<%= config.bin %> token list --kind daemon',
|
|
32
|
+
'<%= config.bin %> token list --kind cli --limit 500 --json',
|
|
25
33
|
];
|
|
26
|
-
static flags = {
|
|
34
|
+
static flags = {
|
|
35
|
+
kind: core_1.Flags.string({
|
|
36
|
+
description: 'Only sessions of this kind',
|
|
37
|
+
options: [...SESSION_KINDS],
|
|
38
|
+
}),
|
|
39
|
+
limit: core_1.Flags.integer({
|
|
40
|
+
description: 'Most sessions to list (server maximum 500)',
|
|
41
|
+
min: 1,
|
|
42
|
+
max: 500,
|
|
43
|
+
}),
|
|
44
|
+
json: core_1.Flags.boolean({ description: 'Output JSON' }),
|
|
45
|
+
};
|
|
27
46
|
async run() {
|
|
28
47
|
this.requireAuth();
|
|
29
48
|
const { flags } = await this.parse(TokenList);
|
|
49
|
+
const query = new URLSearchParams();
|
|
50
|
+
if (flags.kind)
|
|
51
|
+
query.set('kind', flags.kind);
|
|
52
|
+
if (flags.limit)
|
|
53
|
+
query.set('limit', String(flags.limit));
|
|
54
|
+
const suffix = query.toString() ? `?${query.toString()}` : '';
|
|
30
55
|
let data = null;
|
|
31
56
|
try {
|
|
32
|
-
data = await (0, api_fetch_1.apiFetch)(
|
|
57
|
+
data = await (0, api_fetch_1.apiFetch)(`/api/auth/sessions/refresh${suffix}`, {
|
|
33
58
|
method: 'GET',
|
|
34
59
|
credential: this.resolvedCredential,
|
|
35
60
|
});
|
|
@@ -37,9 +62,17 @@ class TokenList extends base_command_1.BaseCommand {
|
|
|
37
62
|
catch (err) {
|
|
38
63
|
this.handleApiError(err);
|
|
39
64
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
// Filtered here as well: a server from before `kind` existed ignores the
|
|
66
|
+
// parameter and returns every kind, and `--kind daemon` must never list a
|
|
67
|
+
// web session as if it were one.
|
|
68
|
+
const sessions = (data?.sessions || []).filter((session) => !flags.kind || session.kind === flags.kind);
|
|
69
|
+
// A server from before `hasMore` stopped at 100 and said nothing about it,
|
|
70
|
+
// and ignores `--limit` too — so it gets its own, weaker statement.
|
|
71
|
+
const olderServer = data?.hasMore === undefined;
|
|
72
|
+
const hasMore = olderServer ? (data?.sessions?.length ?? 0) >= 100 : data?.hasMore === true;
|
|
73
|
+
if (flags.json) {
|
|
74
|
+
return this.log(JSON.stringify({ sessions, count: sessions.length, hasMore }, null, 2));
|
|
75
|
+
}
|
|
43
76
|
if (!sessions.length)
|
|
44
77
|
return this.log('No active sessions.');
|
|
45
78
|
(0, format_1.renderTable)(sessions.map((session) => ({
|
|
@@ -48,6 +81,8 @@ class TokenList extends base_command_1.BaseCommand {
|
|
|
48
81
|
// The operator's own name for the credential wins over a derived
|
|
49
82
|
// device string — it is what they would search by.
|
|
50
83
|
device: session.label || session.device || '-',
|
|
84
|
+
// What `instructions`, `machines` and a purge refusal call it.
|
|
85
|
+
daemon: session.daemonId || '-',
|
|
51
86
|
ip: session.ip || '-',
|
|
52
87
|
lastActive: session.lastActiveAt || session.createdAt || '-',
|
|
53
88
|
// The session you are typing in. Revoking it signs THIS machine out,
|
|
@@ -57,11 +92,21 @@ class TokenList extends base_command_1.BaseCommand {
|
|
|
57
92
|
{ key: 'id', header: 'ID' },
|
|
58
93
|
{ key: 'kind', header: 'KIND' },
|
|
59
94
|
{ key: 'device', header: 'DEVICE', maxWidth: 34 },
|
|
95
|
+
{ key: 'daemon', header: 'DAEMON', maxWidth: 40 },
|
|
60
96
|
{ key: 'ip', header: 'IP' },
|
|
61
97
|
{ key: 'lastActive', header: 'LAST ACTIVE' },
|
|
62
98
|
{ key: 'current', header: 'CURRENT' },
|
|
63
99
|
], (line) => this.log(line));
|
|
64
100
|
this.log('');
|
|
101
|
+
if (hasMore && olderServer) {
|
|
102
|
+
this.log('This server lists at most 100 sessions and does not say whether it left any out, so older ones may be missing.');
|
|
103
|
+
}
|
|
104
|
+
else if (hasMore) {
|
|
105
|
+
const remedy = flags.kind
|
|
106
|
+
? 'Raise --limit (up to 500) to see them.'
|
|
107
|
+
: 'Narrow the list with --kind, or raise --limit (up to 500).';
|
|
108
|
+
this.log(`Showing the ${sessions.length} most recently active sessions; older ones were left out. ${remedy}`);
|
|
109
|
+
}
|
|
65
110
|
this.log('Retire one with: skrr token revoke <id>');
|
|
66
111
|
}
|
|
67
112
|
}
|
package/dist/help.js
CHANGED
|
@@ -46,26 +46,22 @@ class SkrrHelp extends core_1.Help {
|
|
|
46
46
|
*/
|
|
47
47
|
async showRootHelp() {
|
|
48
48
|
this.log('GETTING STARTED');
|
|
49
|
-
this.log(' skrr login Sign in. Offers to
|
|
50
|
-
this.log(' skrr daemon install Install the
|
|
49
|
+
this.log(' skrr login Sign in. Offers to install the background service if this computer has none.');
|
|
50
|
+
this.log(' skrr daemon install Install the background service by hand (fetches the signed release).');
|
|
51
51
|
this.log('');
|
|
52
52
|
return super.showRootHelp();
|
|
53
53
|
}
|
|
54
54
|
async showHelp(argv) {
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
break;
|
|
61
|
-
tokens.push(token);
|
|
62
|
-
}
|
|
63
|
-
const id = tokens.join(':');
|
|
55
|
+
// Resolve the id exactly as running the same words would — a word after a
|
|
56
|
+
// command that takes arguments is an argument, not part of the name
|
|
57
|
+
// (OSK-10099: `harnesses install devin --help` was refused).
|
|
58
|
+
const lookup = (0, command_miss_1.lookupFromConfig)(this.config);
|
|
59
|
+
const id = (0, command_miss_1.helpSubjectId)(argv, lookup);
|
|
64
60
|
// A bare topic (`skrr daemon`) has no command but DOES have help to show,
|
|
65
61
|
// so it must fall through — only an id that is neither a command nor a
|
|
66
62
|
// topic is a miss.
|
|
67
63
|
if (id && !this.config.findCommand(id) && !this.config.findTopic(id)) {
|
|
68
|
-
throw new Error((0, command_miss_1.describeCommandMiss)(
|
|
64
|
+
throw new Error((0, command_miss_1.describeCommandMiss)(lookup, id));
|
|
69
65
|
}
|
|
70
66
|
return super.showHelp(argv);
|
|
71
67
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace an `agents chat` turn runs in when the caller named none.
|
|
3
|
+
*
|
|
4
|
+
* A turn with no workspace context reaches the server without `X-Workspace-Id`.
|
|
5
|
+
* The server lets it through after checking membership in the agent's own
|
|
6
|
+
* workspace, but the agent runtime then has no workspace, so the shared
|
|
7
|
+
* workspace memory tier refuses with "no workspace context" (OSK-9937). By
|
|
8
|
+
* owner decision the CLI now uses the agent's home workspace in that case: the
|
|
9
|
+
* same thing the web app sends, and the only workspace the server would accept
|
|
10
|
+
* for that agent anyway.
|
|
11
|
+
*
|
|
12
|
+
* Never applied when anything already chose: `--workspace`, an exported
|
|
13
|
+
* `OVERSKY_WORKSPACE_ID`, or `skrr workspaces use` (all arrive as that env var),
|
|
14
|
+
* and never inside an autonomous Agent process, whose workspace belongs to its
|
|
15
|
+
* daemon session.
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyAgentHomeWorkspaceDefault(agentId: string, lookup: (agentId: string) => Promise<{
|
|
18
|
+
workspaceId?: string | null;
|
|
19
|
+
} | null | undefined>, env?: NodeJS.ProcessEnv): Promise<string | null>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyAgentHomeWorkspaceDefault = applyAgentHomeWorkspaceDefault;
|
|
4
|
+
/**
|
|
5
|
+
* The workspace an `agents chat` turn runs in when the caller named none.
|
|
6
|
+
*
|
|
7
|
+
* A turn with no workspace context reaches the server without `X-Workspace-Id`.
|
|
8
|
+
* The server lets it through after checking membership in the agent's own
|
|
9
|
+
* workspace, but the agent runtime then has no workspace, so the shared
|
|
10
|
+
* workspace memory tier refuses with "no workspace context" (OSK-9937). By
|
|
11
|
+
* owner decision the CLI now uses the agent's home workspace in that case: the
|
|
12
|
+
* same thing the web app sends, and the only workspace the server would accept
|
|
13
|
+
* for that agent anyway.
|
|
14
|
+
*
|
|
15
|
+
* Never applied when anything already chose: `--workspace`, an exported
|
|
16
|
+
* `OVERSKY_WORKSPACE_ID`, or `skrr workspaces use` (all arrive as that env var),
|
|
17
|
+
* and never inside an autonomous Agent process, whose workspace belongs to its
|
|
18
|
+
* daemon session.
|
|
19
|
+
*/
|
|
20
|
+
async function applyAgentHomeWorkspaceDefault(agentId, lookup, env = process.env) {
|
|
21
|
+
if (env.OVERSKY_AUTONOMOUS_SESSION === '1')
|
|
22
|
+
return null;
|
|
23
|
+
if (env.OVERSKY_WORKSPACE_ID?.trim())
|
|
24
|
+
return null;
|
|
25
|
+
let agent;
|
|
26
|
+
try {
|
|
27
|
+
agent = await lookup(agentId);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Unreadable agent: keep the headerless request. The server still decides
|
|
31
|
+
// access, and the chat call itself reports a missing or forbidden agent.
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const workspaceId = typeof agent?.workspaceId === 'string' ? agent.workspaceId.trim() : '';
|
|
35
|
+
if (!workspaceId)
|
|
36
|
+
return null;
|
|
37
|
+
env.OVERSKY_WORKSPACE_ID = workspaceId;
|
|
38
|
+
return workspaceId;
|
|
39
|
+
}
|
|
@@ -38,7 +38,7 @@ async function resolveAgentIdentity(explicitAgentId) {
|
|
|
38
38
|
if (explicitAgentId && explicitAgentId !== runtime.agent.id) {
|
|
39
39
|
throw new AgentResolutionError({
|
|
40
40
|
code: 'AGENT_CONTEXT_CONFLICT',
|
|
41
|
-
message: 'This
|
|
41
|
+
message: 'This session is bound to the agent in OVERSKY_SESSION_AGENT_ID. Do not override it with another agent ID.',
|
|
42
42
|
suggestion: 'Use the bound session agent, or start a new daemon session for a different agent.',
|
|
43
43
|
details: {
|
|
44
44
|
explicitAgentId,
|
|
@@ -129,7 +129,7 @@ function resolveSessionAgentIdentity(explicitAgentId, env = process.env) {
|
|
|
129
129
|
if (explicitAgentId && explicitAgentId !== runtime.agent.id) {
|
|
130
130
|
throw new AgentResolutionError({
|
|
131
131
|
code: 'AGENT_CONTEXT_CONFLICT',
|
|
132
|
-
message: 'This
|
|
132
|
+
message: 'This session is bound to the agent in OVERSKY_SESSION_AGENT_ID. Do not override it with another agent ID.',
|
|
133
133
|
suggestion: 'Use the bound session agent, or start a new daemon session for a different agent.',
|
|
134
134
|
details: {
|
|
135
135
|
explicitAgentId,
|
|
@@ -85,6 +85,8 @@ export type AgenticStreamOptions = {
|
|
|
85
85
|
reconcileTerminal?: () => Promise<ReconciledTerminal | null>;
|
|
86
86
|
/** Silence before the first durable-record probe. Default 20s. */
|
|
87
87
|
idleTerminalProbeMs?: number;
|
|
88
|
+
/** How long to keep trying to attach before reporting a detach. Default 30s. */
|
|
89
|
+
attachTimeoutMs?: number;
|
|
88
90
|
};
|
|
89
91
|
/**
|
|
90
92
|
* Small Node-side adapter for the existing Agentic Socket.IO contract.
|
|
@@ -95,7 +97,12 @@ export type AgenticStreamOptions = {
|
|
|
95
97
|
* The assistant text inside a `session:output` frame's envelopes.
|
|
96
98
|
*
|
|
97
99
|
* Envelope shape: `{ ev: { t: 'text', text: '…' } }`. Frames whose `ev.t` is
|
|
98
|
-
* anything else (tool calls, status) are not the answer and are skipped
|
|
100
|
+
* anything else (tool calls, status) are not the answer and are skipped, and so
|
|
101
|
+
* is text marked `thinking: true` — the model's reasoning, which the session
|
|
102
|
+
* protocol carries in the same `text` event. Folded in, a Devin turn asked for
|
|
103
|
+
* one word printed "I need to output only the word SECOND, … single
|
|
104
|
+
* word.SECOND" as its answer, and `run.result.text` kept it, because the
|
|
105
|
+
* stream then held the final result as a suffix and the longer text won.
|
|
99
106
|
*/
|
|
100
107
|
export declare function envelopesText(event: AgenticStreamEvent): string;
|
|
101
108
|
/**
|
|
@@ -281,6 +288,8 @@ export declare class AgenticStreamClient {
|
|
|
281
288
|
/** Text already written to the terminal, for suffix-only rendering. */
|
|
282
289
|
private renderedText;
|
|
283
290
|
private idleTimer;
|
|
291
|
+
/** Releases a connect wait still in flight; `close()` calls it. */
|
|
292
|
+
private abandonConnectWait;
|
|
284
293
|
private reconciling;
|
|
285
294
|
private responseMessageId;
|
|
286
295
|
private lastEvent;
|
|
@@ -307,6 +316,14 @@ export declare class AgenticStreamClient {
|
|
|
307
316
|
* rather than giving up — a 200-second tool call is silence, not a failure —
|
|
308
317
|
* so the cost of being wrong is one cheap read, while the cost of not asking
|
|
309
318
|
* was an interactive session that never returned.
|
|
319
|
+
*
|
|
320
|
+
* The timer is deliberately NOT unref'd. It exists for the moment the socket
|
|
321
|
+
* stops holding the process open — a server-side disconnect, or a reconnect
|
|
322
|
+
* the server refuses, after which socket.io does not retry. Unref'd, it was
|
|
323
|
+
* the last thing pending at exactly that moment, so Node's event loop emptied
|
|
324
|
+
* and the CLI exited 0 with the run unsettled: `--no-stream --json` printed
|
|
325
|
+
* nothing at all for a turn the record said had failed (OSK-10061). A run
|
|
326
|
+
* that has not settled is a reason to stay alive; `close()` clears the timer.
|
|
310
327
|
*/
|
|
311
328
|
private armIdleProbe;
|
|
312
329
|
/**
|
|
@@ -364,6 +381,10 @@ export declare class AgenticStreamClient {
|
|
|
364
381
|
* Now errors are surfaced as events and retried until a bounded deadline, so
|
|
365
382
|
* only a sustained inability to attach gives up — and the caller turns that
|
|
366
383
|
* into "accepted but detached", never "failed".
|
|
384
|
+
*
|
|
385
|
+
* The deadline and retry timers hold the process open (see `armDeadline`), so
|
|
386
|
+
* `close()` is what releases them — and it ends the wait too, whether the
|
|
387
|
+
* caller gave up or the durable record settled the run first (OSK-10061).
|
|
367
388
|
*/
|
|
368
389
|
private waitForConnect;
|
|
369
390
|
}
|
|
@@ -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);
|
|
@@ -25,6 +25,13 @@ 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
37
|
job?: {
|
|
@@ -44,7 +51,7 @@ export interface CliInstallersResponse {
|
|
|
44
51
|
* Providers the platform knows about that this daemon did not offer.
|
|
45
52
|
*
|
|
46
53
|
* The inventory is whatever the CONNECTED daemon supports, and an older daemon
|
|
47
|
-
* legitimately supports fewer: `devin` arrived in
|
|
54
|
+
* legitimately supports fewer: `devin` arrived in the 0.8.45 daemon release, so a machine
|
|
48
55
|
* running 0.8.35 answers with six providers instead of seven. Observed here —
|
|
49
56
|
* the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
|
|
50
57
|
* disappeared from the install list between two page loads, with nothing on
|
|
@@ -56,8 +63,28 @@ export interface CliInstallersResponse {
|
|
|
56
63
|
* is already on this side of the wire. Naming the gap is what turns "Devin is
|
|
57
64
|
* gone" into "this daemon is older than Devin".
|
|
58
65
|
*/
|
|
66
|
+
/**
|
|
67
|
+
* The first STABLE daemon release (`daemon-v*` tag) whose
|
|
68
|
+
* MANAGED_CLI_PROVIDER_IDS included each provider. Release tags, not
|
|
69
|
+
* package.json at the commit that added it: the version is bumped before a
|
|
70
|
+
* release is cut, so the commit that added devin already read 0.8.43 while the
|
|
71
|
+
* 0.8.43 release did not contain it — this table said "added in 0.8.43" to a
|
|
72
|
+
* 0.8.43 user (OSK-9537 verifier). Derived with, per provider:
|
|
73
|
+
* for t in $(git tag -l 'daemon-v*' | sort -V); do
|
|
74
|
+
* git show "$t:daemon/src/cli-installers.ts" | grep -q "'devin'" && echo "$t" && break
|
|
75
|
+
* done
|
|
76
|
+
* Providers present since managed installers first shipped (0.8.1) are omitted.
|
|
77
|
+
*/
|
|
78
|
+
export declare const MANAGED_CLI_PROVIDER_SINCE: Readonly<Record<string, string>>;
|
|
79
|
+
/**
|
|
80
|
+
* "Not offered" with the release that added each provider, so a reader learns
|
|
81
|
+
* WHICH update fixes it rather than only that one might (OSK-9537).
|
|
82
|
+
*/
|
|
83
|
+
export declare function describeProvidersNotOffered(missing: readonly string[]): string;
|
|
59
84
|
export declare function providersNotOffered(offered: readonly ManagedCliProviderState[] | undefined, known: readonly string[]): string[];
|
|
60
|
-
export declare function listCliInstallers(daemonId: string, credential?: unknown
|
|
85
|
+
export declare function listCliInstallers(daemonId: string, credential?: unknown, opts?: {
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
}): Promise<CliInstallersResponse>;
|
|
61
88
|
/**
|
|
62
89
|
* The one-word readiness verdict for a provider, and whether it is good news.
|
|
63
90
|
*
|
|
@@ -74,14 +101,82 @@ export declare function readinessOf(provider: ManagedCliProviderState): {
|
|
|
74
101
|
ok: boolean;
|
|
75
102
|
};
|
|
76
103
|
/** A job the daemon runs to install one managed provider. */
|
|
104
|
+
/**
|
|
105
|
+
* Readiness per provider for one daemon, keyed by CANONICAL provider — or null
|
|
106
|
+
* when the daemon could not be asked (disconnected, older, refused).
|
|
107
|
+
*
|
|
108
|
+
* The harness row's `authStatus` is the DAEMON's standing with skrr, written
|
|
109
|
+
* across every row of a daemon at once, so `harnesses show`, `machines show`
|
|
110
|
+
* and `ping` answered "ok" per provider for a Devin its own CLI called "Not
|
|
111
|
+
* logged in" (OSK-9263, OSK-9265). This is the per-provider answer those
|
|
112
|
+
* surfaces print instead. Best-effort by design: a failure here must never
|
|
113
|
+
* fail the command that asked.
|
|
114
|
+
*/
|
|
115
|
+
export declare function readinessByProvider(daemonId: string, credential?: unknown, opts?: {
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}): Promise<Map<string, ReturnType<typeof readinessOf>> | null>;
|
|
118
|
+
/** The canonical provider a harness row names, for looking it up in that map. */
|
|
119
|
+
export declare function readinessKey(provider: string | null | undefined): string;
|
|
120
|
+
export type ProviderReadiness = ReturnType<typeof readinessOf>;
|
|
121
|
+
/** The fields of a harness row that decide whether, and whom, to ask. */
|
|
122
|
+
export interface ReadinessRow {
|
|
123
|
+
daemonId?: string | null;
|
|
124
|
+
provider?: string | null;
|
|
125
|
+
status?: string | null;
|
|
126
|
+
superseded?: boolean;
|
|
127
|
+
}
|
|
128
|
+
/** How long one daemon may take to answer before a list prints `-` for it. */
|
|
129
|
+
export declare const ROW_READINESS_TIMEOUT_MS = 5000;
|
|
130
|
+
/**
|
|
131
|
+
* Readiness for many harness rows at once: ONE installer read per connected
|
|
132
|
+
* daemon, all in parallel, however many rows each daemon has.
|
|
133
|
+
*
|
|
134
|
+
* Only a row whose daemon can answer is asked for — online and not superseded;
|
|
135
|
+
* an offline or replaced registration has nobody on the other end. Each read is
|
|
136
|
+
* bounded, because a list must not hang on one slow machine; a daemon that does
|
|
137
|
+
* not answer in time reads as unknown (`undefined`), never as `ready`.
|
|
138
|
+
*
|
|
139
|
+
* `asked` and `answered` let a caller tell "this daemon does not report it"
|
|
140
|
+
* from "nothing could be asked", which call for different next steps.
|
|
141
|
+
*/
|
|
142
|
+
export declare function readinessForRows(rows: readonly ReadinessRow[], credential?: unknown, opts?: {
|
|
143
|
+
timeoutMs?: number;
|
|
144
|
+
}): Promise<{
|
|
145
|
+
lookup: (row: ReadinessRow) => ProviderReadiness | undefined;
|
|
146
|
+
/** Whether this row's daemon answered at all — so a `-` can say why. */
|
|
147
|
+
daemonAnswered: (row: ReadinessRow) => boolean;
|
|
148
|
+
asked: number;
|
|
149
|
+
answered: number;
|
|
150
|
+
}>;
|
|
77
151
|
export interface ManagedCliInstallJob {
|
|
78
152
|
id: string;
|
|
153
|
+
provider?: string;
|
|
154
|
+
displayName?: string;
|
|
79
155
|
status: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
156
|
+
/** `update` for an in-place update; absent from an older daemon, meaning install. */
|
|
157
|
+
kind?: 'install' | 'update';
|
|
158
|
+
previousVersion?: string | null;
|
|
159
|
+
installedVersion?: string | null;
|
|
80
160
|
error?: string;
|
|
161
|
+
/**
|
|
162
|
+
* The daemon's job log: the LAST lines only (a sliding window, 200 lines in
|
|
163
|
+
* `daemon/src/cli-installers.ts`). This is the field the daemon sends.
|
|
164
|
+
*/
|
|
165
|
+
logTail?: string[];
|
|
166
|
+
/**
|
|
167
|
+
* Lines the job has EVER logged (daemons with OSK-10179). With it, the lines
|
|
168
|
+
* new since the last poll are exactly the last `count − printed` of the tail,
|
|
169
|
+
* even when they repeat.
|
|
170
|
+
*/
|
|
171
|
+
logLineCount?: number;
|
|
172
|
+
/** Never sent by any daemon; kept so an older caller's shape still types. */
|
|
81
173
|
log?: string[];
|
|
82
174
|
}
|
|
83
175
|
export interface StartInstallResponse {
|
|
84
176
|
started?: boolean;
|
|
177
|
+
/** The daemon declined because the CLI is already there. */
|
|
178
|
+
alreadyInstalled?: boolean;
|
|
179
|
+
alreadyRunning?: boolean;
|
|
85
180
|
provider?: ManagedCliProviderState;
|
|
86
181
|
job?: {
|
|
87
182
|
id?: string;
|
|
@@ -89,9 +184,36 @@ export interface StartInstallResponse {
|
|
|
89
184
|
};
|
|
90
185
|
}
|
|
91
186
|
export declare function startManagedCliInstall(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
|
|
187
|
+
/** Ask the daemon to update an installed CLI in place (OSK-10096). */
|
|
188
|
+
export declare function startManagedCliUpdate(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
|
|
189
|
+
/**
|
|
190
|
+
* How an update job ended, from the versions the daemon recorded on it:
|
|
191
|
+
* "Updated Claude Code 2.1.270 → 2.1.280." or "Claude Code is still 2.1.270:
|
|
192
|
+
* its updater found nothing newer." Null when the job does not carry both.
|
|
193
|
+
*/
|
|
194
|
+
export declare function updateOutcomeLine(job: ManagedCliInstallJob): string | null;
|
|
92
195
|
export declare function readManagedCliInstallJob(daemonId: string, jobId: string, credential?: unknown): Promise<{
|
|
93
196
|
job?: ManagedCliInstallJob;
|
|
94
197
|
}>;
|
|
198
|
+
/**
|
|
199
|
+
* The lines of a job's log tail not yet printed.
|
|
200
|
+
*
|
|
201
|
+
* The tail is a SLIDING window: once an installer writes more than the daemon
|
|
202
|
+
* keeps, earlier lines fall off the front, so an index into it skips or repeats
|
|
203
|
+
* lines. Instead, find the longest run at the start of the new tail that matches
|
|
204
|
+
* the end of what was already printed, and print only what follows it.
|
|
205
|
+
*/
|
|
206
|
+
export declare function unseenLogLines(printed: readonly string[], tail: readonly string[]): string[];
|
|
207
|
+
/**
|
|
208
|
+
* The new lines of a job's tail when the daemon reports its running line count:
|
|
209
|
+
* exactly the last `total − printedCount`, and how many of those already fell
|
|
210
|
+
* out of the window (the daemon keeps the last 200). Unlike overlap matching,
|
|
211
|
+
* this cannot mistake a repeated line for one already printed.
|
|
212
|
+
*/
|
|
213
|
+
export declare function newLogLinesByCount(printedCount: number, tail: readonly string[], total: number): {
|
|
214
|
+
lines: string[];
|
|
215
|
+
skipped: number;
|
|
216
|
+
};
|
|
95
217
|
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
96
218
|
export declare const INSTALL_MAX_WAIT_MS: number;
|
|
97
219
|
export declare const INSTALL_POLL_INTERVAL_MS = 2000;
|