@skrr-ai/cli 0.1.86 → 0.1.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run.js +11 -0
- package/dist/base-command.js +1 -0
- package/dist/commands/agents/chat.d.ts +58 -1
- package/dist/commands/agents/chat.js +206 -4
- package/dist/commands/computer/index.d.ts +19 -0
- package/dist/commands/computer/index.js +37 -0
- package/dist/commands/labels/create.d.ts +1 -0
- package/dist/commands/labels/create.js +2 -0
- package/dist/commands/labels/restore.d.ts +1 -0
- package/dist/commands/labels/restore.js +6 -3
- package/dist/commands/labels/update.d.ts +1 -0
- package/dist/commands/labels/update.js +2 -0
- package/dist/commands/machines/computer-adoption.d.ts +15 -0
- package/dist/commands/machines/computer-adoption.js +33 -0
- package/dist/commands/machines/dedicated/archive.d.ts +26 -0
- package/dist/commands/machines/dedicated/archive.js +113 -0
- package/dist/commands/machines/dedicated/audit.d.ts +25 -0
- package/dist/commands/machines/dedicated/audit.js +113 -0
- package/dist/commands/machines/dedicated/computer.d.ts +23 -0
- package/dist/commands/machines/dedicated/computer.js +64 -0
- package/dist/commands/machines/dedicated/cp.d.ts +2 -0
- package/dist/commands/machines/dedicated/cp.js +74 -2
- package/dist/commands/machines/dedicated/git-status.d.ts +15 -0
- package/dist/commands/machines/dedicated/git-status.js +56 -0
- package/dist/commands/machines/dedicated/index.js +15 -1
- package/dist/commands/machines/dedicated/ls.d.ts +27 -0
- package/dist/commands/machines/dedicated/ls.js +100 -0
- package/dist/commands/machines/dedicated/mkdir.d.ts +18 -0
- package/dist/commands/machines/dedicated/mkdir.js +57 -0
- package/dist/commands/machines/dedicated/mv.d.ts +24 -0
- package/dist/commands/machines/dedicated/mv.js +83 -0
- package/dist/commands/machines/dedicated/rm.d.ts +26 -0
- package/dist/commands/machines/dedicated/rm.js +100 -0
- package/dist/commands/machines/dedicated/search.d.ts +24 -0
- package/dist/commands/machines/dedicated/search.js +78 -0
- package/dist/commands/machines/dedicated/stat.d.ts +22 -0
- package/dist/commands/machines/dedicated/stat.js +92 -0
- package/dist/commands/machines/hosted/index.js +1 -1
- package/dist/commands/machines/hosted/list.js +1 -1
- package/dist/commands/machines/hosted/start.js +1 -1
- package/dist/commands/machines/services/declare.d.ts +24 -0
- package/dist/commands/machines/services/declare.js +73 -0
- package/dist/commands/machines/services/index.d.ts +15 -0
- package/dist/commands/machines/services/index.js +31 -0
- package/dist/commands/machines/services/ls.d.ts +17 -0
- package/dist/commands/machines/services/ls.js +60 -0
- package/dist/commands/machines/services/withdraw.d.ts +19 -0
- package/dist/commands/machines/services/withdraw.js +47 -0
- package/dist/commands/machines/share.d.ts +29 -0
- package/dist/commands/machines/share.js +100 -0
- package/dist/commands/machines/shared.d.ts +19 -0
- package/dist/commands/machines/shared.js +62 -0
- package/dist/commands/machines/shares.d.ts +16 -0
- package/dist/commands/machines/shares.js +69 -0
- package/dist/commands/machines/unshare.d.ts +19 -0
- package/dist/commands/machines/unshare.js +61 -0
- package/dist/commands/tasks/labels/create.d.ts +1 -0
- package/dist/commands/tasks/labels/create.js +3 -0
- package/dist/commands/views/create.d.ts +1 -0
- package/dist/commands/views/create.js +5 -0
- package/dist/lib/agent-home-workspace.d.ts +26 -0
- package/dist/lib/agent-home-workspace.js +174 -0
- package/dist/lib/agentic-stream.d.ts +135 -0
- package/dist/lib/agentic-stream.js +341 -6
- package/dist/lib/auth-storage.d.ts +4 -0
- package/dist/lib/auth-storage.js +33 -0
- package/dist/lib/computer-adoption.d.ts +55 -0
- package/dist/lib/computer-adoption.js +67 -0
- package/dist/lib/computer-consent.d.ts +13 -0
- package/dist/lib/computer-consent.js +33 -0
- package/dist/lib/computer-files.d.ts +239 -0
- package/dist/lib/computer-files.js +707 -0
- package/dist/lib/dedicated-copy.d.ts +45 -9
- package/dist/lib/dedicated-copy.js +141 -44
- package/dist/lib/dedicated-machines.d.ts +49 -0
- package/dist/lib/dedicated-machines.js +97 -6
- package/dist/lib/keychain.d.ts +1 -0
- package/dist/lib/keychain.js +67 -14
- package/dist/lib/label-scope.d.ts +12 -0
- package/dist/lib/label-scope.js +15 -1
- package/dist/lib/machine-audit.d.ts +34 -0
- package/dist/lib/machine-audit.js +50 -0
- package/dist/lib/machine-grants.d.ts +70 -0
- package/dist/lib/machine-grants.js +47 -0
- package/dist/lib/machine-services.d.ts +64 -0
- package/dist/lib/machine-services.js +60 -0
- package/dist/lib/refresh.d.ts +7 -0
- package/dist/lib/refresh.js +2 -1
- package/dist/lib/task-view-render.d.ts +2 -0
- package/dist/lib/task-view-render.js +1 -1
- package/dist/lib/views/vocabulary.d.ts +1 -1
- package/dist/lib/views/vocabulary.js +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +955 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +1070 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +955 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +1051 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +22143 -20728
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +18855 -17260
- package/package.json +2 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const machines_1 = require("../../lib/machines");
|
|
6
|
+
const machine_grants_1 = require("../../lib/machine-grants");
|
|
7
|
+
/**
|
|
8
|
+
* `skrr machines unshare` — revoke one person's grant on a machine (design §9;
|
|
9
|
+
* OSK-13558). Revocation bumps the machine's grant-set version, so its daemon
|
|
10
|
+
* detaches that person's attachments and releases any lease they hold when
|
|
11
|
+
* the new set arrives. Reversible — `machines share` grants again.
|
|
12
|
+
*/
|
|
13
|
+
class MachinesUnshare extends base_command_1.BaseCommand {
|
|
14
|
+
static description = 'Stop sharing a machine with a person — detaches their surfaces and releases their control lease';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> machines unshare 03548116 68db4b2f0b4f5a9c1d2e3f40',
|
|
17
|
+
];
|
|
18
|
+
static args = {
|
|
19
|
+
machine: core_1.Args.string({
|
|
20
|
+
ignoreStdin: true,
|
|
21
|
+
description: 'Machine id, uuid, daemon id, name, or a unique prefix',
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
user: core_1.Args.string({
|
|
25
|
+
ignoreStdin: true,
|
|
26
|
+
description: 'The user id of the person to stop sharing with',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static flags = {
|
|
31
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
this.requireAuth();
|
|
35
|
+
const { args, flags } = await this.parse(MachinesUnshare);
|
|
36
|
+
let target;
|
|
37
|
+
try {
|
|
38
|
+
target = (0, machines_1.resolveMachineRef)(await (0, machines_1.listMachines)(), args.machine);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
this.handleApiError(err);
|
|
42
|
+
}
|
|
43
|
+
if (!target) {
|
|
44
|
+
this.error('Machine not found.', { exit: 1 });
|
|
45
|
+
}
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await (0, machine_grants_1.unshareMachine)(target.id, args.user);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
this.handleApiError(err);
|
|
52
|
+
}
|
|
53
|
+
if (flags.json) {
|
|
54
|
+
this.log(JSON.stringify(result, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const label = target.preferences?.name || target.name || target.id;
|
|
58
|
+
this.log(`Stopped sharing ${label} with ${args.user}.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.default = MachinesUnshare;
|
|
@@ -3,6 +3,7 @@ export default class TasksLabelsCreate extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
'allow-duplicate-name': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
7
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
'from-json': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
9
|
name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -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 prompt_1 = require("../../../lib/prompt");
|
|
7
|
+
const label_scope_1 = require("../../../lib/label-scope");
|
|
7
8
|
// Accept both `#e11d48` and `e11d48` forms; the server's Zod regex
|
|
8
9
|
// (`api/server/routes/tasks.js:206`) is `/^[0-9a-fA-F]{6}$/` — NO leading
|
|
9
10
|
// `#`. We normalize before sending so users can pass either form.
|
|
@@ -24,6 +25,7 @@ class TasksLabelsCreate extends base_command_1.BaseCommand {
|
|
|
24
25
|
color: core_1.Flags.string({
|
|
25
26
|
description: 'Label color as 6-digit hex, e.g. e11d48 (leading # is accepted and stripped)',
|
|
26
27
|
}),
|
|
28
|
+
...label_scope_1.allowDuplicateNameFlags,
|
|
27
29
|
};
|
|
28
30
|
async run() {
|
|
29
31
|
this.requireAuth();
|
|
@@ -38,6 +40,7 @@ class TasksLabelsCreate extends base_command_1.BaseCommand {
|
|
|
38
40
|
const payload = (0, prompt_1.mergeDefined)(jsonBody, {
|
|
39
41
|
name: flags.name,
|
|
40
42
|
color: flags.color,
|
|
43
|
+
allowDuplicateName: flags['allow-duplicate-name'] ? true : undefined,
|
|
41
44
|
});
|
|
42
45
|
if (!payload.name || typeof payload.name !== 'string') {
|
|
43
46
|
this.error('A label name is required. Pass --name "..." or include it in --from-json.', {
|
|
@@ -14,6 +14,7 @@ export default class ViewsCreate extends BaseCommand {
|
|
|
14
14
|
priority: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
15
|
assignee: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
16
|
label: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
+
'label-match': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
18
|
goal: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
19
|
project: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
20
|
search: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -35,6 +35,10 @@ class ViewsCreate extends base_command_1.BaseCommand {
|
|
|
35
35
|
}),
|
|
36
36
|
assignee: core_1.Flags.string({ multiple: true, description: 'Filter by agent or user ID' }),
|
|
37
37
|
label: core_1.Flags.string({ multiple: true, description: 'Filter by label ID' }),
|
|
38
|
+
'label-match': core_1.Flags.string({
|
|
39
|
+
options: [...vocabulary_1.VIEW_LABEL_MATCH_VALUES],
|
|
40
|
+
description: 'How --label values combine: any (default) or all',
|
|
41
|
+
}),
|
|
38
42
|
goal: core_1.Flags.string({ multiple: true, description: 'Filter by goal ID' }),
|
|
39
43
|
project: core_1.Flags.string({ multiple: true, description: 'Filter by project ID' }),
|
|
40
44
|
search: core_1.Flags.string({ description: 'Filter by title, identifier, or description' }),
|
|
@@ -67,6 +71,7 @@ class ViewsCreate extends base_command_1.BaseCommand {
|
|
|
67
71
|
priorities: flags.priority,
|
|
68
72
|
assignees: flags.assignee,
|
|
69
73
|
labels: flags.label,
|
|
74
|
+
labelMatch: flags['label-match'],
|
|
70
75
|
goalIds: flags.goal,
|
|
71
76
|
projectIds: flags.project,
|
|
72
77
|
search: flags.search,
|
|
@@ -17,3 +17,29 @@
|
|
|
17
17
|
export declare function applyAgentHomeWorkspaceDefault(agentId: string, lookup: (agentId: string) => Promise<{
|
|
18
18
|
workspaceId?: string | null;
|
|
19
19
|
} | null | undefined>, env?: NodeJS.ProcessEnv): Promise<string | null>;
|
|
20
|
+
/**
|
|
21
|
+
* The read half of `applyAgentHomeWorkspaceDefault`: the workspace a turn
|
|
22
|
+
* would default to, WITHOUT setting it.
|
|
23
|
+
*
|
|
24
|
+
* Split so `agents chat` can start the agent read at the same time as its
|
|
25
|
+
* other pre-flight reads (`--task`, `--thread-root`, `--local-skill`) instead
|
|
26
|
+
* of after them — each is a full round trip — while those reads still go out
|
|
27
|
+
* exactly as before, with no workspace header. The default is committed only
|
|
28
|
+
* right before the turn is started.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lookupAgentHomeWorkspaceDefault(agentId: string, lookup: (agentId: string) => Promise<{
|
|
31
|
+
workspaceId?: string | null;
|
|
32
|
+
} | null | undefined>, env?: NodeJS.ProcessEnv): Promise<string | null>;
|
|
33
|
+
/**
|
|
34
|
+
* The write half: apply a looked-up default, unless something chose a
|
|
35
|
+
* workspace in the meantime (that choice still wins) or this is an autonomous
|
|
36
|
+
* Agent process.
|
|
37
|
+
*/
|
|
38
|
+
export declare function commitAgentHomeWorkspaceDefault(workspaceId: string | null, env?: NodeJS.ProcessEnv): string | null;
|
|
39
|
+
/** Does this origin's server default a headerless chat turn's workspace itself? */
|
|
40
|
+
export declare function serverAppliesAgentHomeDefault(cacheDir: string | undefined, baseURL: string | undefined, now?: number): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Record what an acceptance says about the server, and return it. Writes only
|
|
43
|
+
* when the verdict changes or the observation is getting old.
|
|
44
|
+
*/
|
|
45
|
+
export declare function recordAgentHomeDefaultObservation(cacheDir: string | undefined, baseURL: string | undefined, accepted: unknown, now?: number): 'supported' | 'unsupported';
|
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.applyAgentHomeWorkspaceDefault = applyAgentHomeWorkspaceDefault;
|
|
37
|
+
exports.lookupAgentHomeWorkspaceDefault = lookupAgentHomeWorkspaceDefault;
|
|
38
|
+
exports.commitAgentHomeWorkspaceDefault = commitAgentHomeWorkspaceDefault;
|
|
39
|
+
exports.serverAppliesAgentHomeDefault = serverAppliesAgentHomeDefault;
|
|
40
|
+
exports.recordAgentHomeDefaultObservation = recordAgentHomeDefaultObservation;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
4
43
|
/**
|
|
5
44
|
* The workspace an `agents chat` turn runs in when the caller named none.
|
|
6
45
|
*
|
|
@@ -18,6 +57,19 @@ exports.applyAgentHomeWorkspaceDefault = applyAgentHomeWorkspaceDefault;
|
|
|
18
57
|
* daemon session.
|
|
19
58
|
*/
|
|
20
59
|
async function applyAgentHomeWorkspaceDefault(agentId, lookup, env = process.env) {
|
|
60
|
+
return commitAgentHomeWorkspaceDefault(await lookupAgentHomeWorkspaceDefault(agentId, lookup, env), env);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The read half of `applyAgentHomeWorkspaceDefault`: the workspace a turn
|
|
64
|
+
* would default to, WITHOUT setting it.
|
|
65
|
+
*
|
|
66
|
+
* Split so `agents chat` can start the agent read at the same time as its
|
|
67
|
+
* other pre-flight reads (`--task`, `--thread-root`, `--local-skill`) instead
|
|
68
|
+
* of after them — each is a full round trip — while those reads still go out
|
|
69
|
+
* exactly as before, with no workspace header. The default is committed only
|
|
70
|
+
* right before the turn is started.
|
|
71
|
+
*/
|
|
72
|
+
async function lookupAgentHomeWorkspaceDefault(agentId, lookup, env = process.env) {
|
|
21
73
|
if (env.OVERSKY_AUTONOMOUS_SESSION === '1')
|
|
22
74
|
return null;
|
|
23
75
|
if (env.OVERSKY_WORKSPACE_ID?.trim())
|
|
@@ -32,8 +84,130 @@ async function applyAgentHomeWorkspaceDefault(agentId, lookup, env = process.env
|
|
|
32
84
|
return null;
|
|
33
85
|
}
|
|
34
86
|
const workspaceId = typeof agent?.workspaceId === 'string' ? agent.workspaceId.trim() : '';
|
|
87
|
+
return workspaceId || null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The write half: apply a looked-up default, unless something chose a
|
|
91
|
+
* workspace in the meantime (that choice still wins) or this is an autonomous
|
|
92
|
+
* Agent process.
|
|
93
|
+
*/
|
|
94
|
+
function commitAgentHomeWorkspaceDefault(workspaceId, env = process.env) {
|
|
35
95
|
if (!workspaceId)
|
|
36
96
|
return null;
|
|
97
|
+
if (env.OVERSKY_AUTONOMOUS_SESSION === '1')
|
|
98
|
+
return null;
|
|
99
|
+
if (env.OVERSKY_WORKSPACE_ID?.trim())
|
|
100
|
+
return null;
|
|
37
101
|
env.OVERSKY_WORKSPACE_ID = workspaceId;
|
|
38
102
|
return workspaceId;
|
|
39
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* OSK-13599 — whether the server this CLI talks to chooses the agent-home
|
|
106
|
+
* workspace itself.
|
|
107
|
+
*
|
|
108
|
+
* A server that does (it defaults a headerless `POST /api/agentic/chat` to the
|
|
109
|
+
* agent's CURRENT home workspace, under exactly the checks the header would
|
|
110
|
+
* pass) makes the pre-flight agent read above redundant, and that read is a
|
|
111
|
+
* full round trip (~480ms at production RTT) on every turn.
|
|
112
|
+
*
|
|
113
|
+
* What is remembered is a fact about the SERVER, never the agent's workspace:
|
|
114
|
+
* agents move between workspaces, so caching their workspace could start a turn
|
|
115
|
+
* in the wrong one, while "this origin applies the default" stays true until
|
|
116
|
+
* the server is rolled back. The evidence is the acceptance itself — a server
|
|
117
|
+
* that applies the default always answers with a `workspaceSource` key (null
|
|
118
|
+
* included); an older one never does. So:
|
|
119
|
+
*
|
|
120
|
+
* - first turn against an origin: no observation → the agent read happens,
|
|
121
|
+
* exactly as before; the acceptance records the observation;
|
|
122
|
+
* - later turns: the read is skipped and the server chooses;
|
|
123
|
+
* - a rollback: the acceptance lacks the key, the observation is dropped and
|
|
124
|
+
* the NEXT turn reads again. The one turn in between ran with no workspace
|
|
125
|
+
* (the pre-OSK-9937 behaviour), which the command reports on stderr.
|
|
126
|
+
*
|
|
127
|
+
* Best-effort everywhere: an unreadable or unwritable cache is "not observed",
|
|
128
|
+
* which is today's behaviour, never an error.
|
|
129
|
+
*/
|
|
130
|
+
const SERVER_FEATURES_FILE = 'server-features.json';
|
|
131
|
+
/** Re-confirm at most this often, so a supported server is not a write per turn. */
|
|
132
|
+
const OBSERVATION_REFRESH_MS = 24 * 60 * 60 * 1000;
|
|
133
|
+
/** An observation nobody has re-confirmed for this long is not trusted. */
|
|
134
|
+
const OBSERVATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
135
|
+
function originOf(baseURL) {
|
|
136
|
+
try {
|
|
137
|
+
return new URL(baseURL).origin;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function featuresPath(cacheDir) {
|
|
144
|
+
return path.join(cacheDir, SERVER_FEATURES_FILE);
|
|
145
|
+
}
|
|
146
|
+
function readFeatures(cacheDir) {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(fs.readFileSync(featuresPath(cacheDir), 'utf8'));
|
|
149
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
150
|
+
? parsed
|
|
151
|
+
: {};
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Does this origin's server default a headerless chat turn's workspace itself? */
|
|
158
|
+
function serverAppliesAgentHomeDefault(cacheDir, baseURL, now = Date.now()) {
|
|
159
|
+
if (!cacheDir || !baseURL)
|
|
160
|
+
return false;
|
|
161
|
+
const origin = originOf(baseURL);
|
|
162
|
+
if (!origin)
|
|
163
|
+
return false;
|
|
164
|
+
const seen = readFeatures(cacheDir)[origin];
|
|
165
|
+
if (seen?.agentHomeWorkspaceDefault !== true)
|
|
166
|
+
return false;
|
|
167
|
+
const at = Date.parse(seen.observedAt);
|
|
168
|
+
return Number.isFinite(at) && now - at <= OBSERVATION_MAX_AGE_MS;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Record what an acceptance says about the server, and return it. Writes only
|
|
172
|
+
* when the verdict changes or the observation is getting old.
|
|
173
|
+
*/
|
|
174
|
+
function recordAgentHomeDefaultObservation(cacheDir, baseURL, accepted, now = Date.now()) {
|
|
175
|
+
const supported = !!accepted && typeof accepted === 'object' && 'workspaceSource' in accepted;
|
|
176
|
+
const verdict = supported ? 'supported' : 'unsupported';
|
|
177
|
+
if (!cacheDir || !baseURL)
|
|
178
|
+
return verdict;
|
|
179
|
+
const origin = originOf(baseURL);
|
|
180
|
+
if (!origin)
|
|
181
|
+
return verdict;
|
|
182
|
+
try {
|
|
183
|
+
const features = readFeatures(cacheDir);
|
|
184
|
+
const previous = features[origin];
|
|
185
|
+
if (!supported && !previous)
|
|
186
|
+
return verdict;
|
|
187
|
+
const previousAt = previous ? Date.parse(previous.observedAt) : NaN;
|
|
188
|
+
if (supported &&
|
|
189
|
+
previous?.agentHomeWorkspaceDefault === true &&
|
|
190
|
+
Number.isFinite(previousAt) &&
|
|
191
|
+
now - previousAt < OBSERVATION_REFRESH_MS) {
|
|
192
|
+
return verdict;
|
|
193
|
+
}
|
|
194
|
+
if (supported) {
|
|
195
|
+
features[origin] = {
|
|
196
|
+
agentHomeWorkspaceDefault: true,
|
|
197
|
+
observedAt: new Date(now).toISOString(),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
delete features[origin];
|
|
202
|
+
}
|
|
203
|
+
fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
|
|
204
|
+
const file = featuresPath(cacheDir);
|
|
205
|
+
const tmp = `${file}.${process.pid}.${now}.tmp`;
|
|
206
|
+
fs.writeFileSync(tmp, JSON.stringify(features), { mode: 0o600 });
|
|
207
|
+
fs.renameSync(tmp, file);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Best-effort: the next turn simply reads the agent again.
|
|
211
|
+
}
|
|
212
|
+
return verdict;
|
|
213
|
+
}
|
|
@@ -20,6 +20,12 @@ export type AgenticStreamResult = {
|
|
|
20
20
|
responseMessageId?: string;
|
|
21
21
|
text: string;
|
|
22
22
|
status: 'completed' | 'aborted' | 'failed';
|
|
23
|
+
/**
|
|
24
|
+
* The run first failed as handed over (a deploy replaced the relaying server)
|
|
25
|
+
* and the machine's completed reply was then repaired onto the record
|
|
26
|
+
* (OSK-13543). Absent on every ordinary run.
|
|
27
|
+
*/
|
|
28
|
+
repaired?: true;
|
|
23
29
|
lastEvent?: AgenticStreamEvent;
|
|
24
30
|
};
|
|
25
31
|
/**
|
|
@@ -45,7 +51,82 @@ export type ReconciledTerminal = {
|
|
|
45
51
|
text?: string;
|
|
46
52
|
/** The recorded failure reason — `metadata.agentic.error` on the message. */
|
|
47
53
|
error?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The failure is a hand-over, not an ending (OSK-13543): the row is failed
|
|
56
|
+
* with {@link DAEMON_HANDOVER_REASON}, which the server itself treats as
|
|
57
|
+
* "outcome pending the machine's own turn-end".
|
|
58
|
+
*/
|
|
59
|
+
handedOver?: boolean;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* OSK-13517 / OSK-13543 — the one failure that is not an ending.
|
|
63
|
+
*
|
|
64
|
+
* A deploy replaced the server RELAYING a local-daemon turn while the daemon
|
|
65
|
+
* kept running it. The server saves the turn `error: true` with this reason
|
|
66
|
+
* (`metadata.agentic.errorSubtype`), keeps the conversation quarantined, and
|
|
67
|
+
* converges the row later from the machine's own turn-end: completed →
|
|
68
|
+
* repaired (`error: false`, `metadata.agentic.deliveryRepair`), anything else →
|
|
69
|
+
* settled failed (`errorSubtype: worker_replaced_daemon_failed`).
|
|
70
|
+
*
|
|
71
|
+
* Mirrors `WORKER_REPLACED_DAEMON_RUNNING_REASON` in
|
|
72
|
+
* `api/server/services/AgentWorker/workerReplacedNotice.js` and the code
|
|
73
|
+
* beside it in `daemonHandlers.js` / `conversationAdmissionClaim.js`; a parity
|
|
74
|
+
* spec reads those files so the two cannot drift.
|
|
75
|
+
*/
|
|
76
|
+
export declare const WORKER_REPLACED_CODE = "AGENT_WORKER_REPLACED";
|
|
77
|
+
export declare const DAEMON_HANDOVER_REASON = "worker_replaced_daemon_running";
|
|
78
|
+
/**
|
|
79
|
+
* Is this terminal event the hand-over failure, and nothing else?
|
|
80
|
+
*
|
|
81
|
+
* The server sends it on two paths with different shapes, so the reason is
|
|
82
|
+
* read wherever either puts it:
|
|
83
|
+
* - the QueueEvents backstop (`queue.js`): `{code: 'AGENT_WORKER_REPLACED',
|
|
84
|
+
* errorType: 'worker_replaced_daemon_running', errors}`;
|
|
85
|
+
* - the worker's own result (`buildExecutionErrorEvent`): `errorType` and
|
|
86
|
+
* `reason` both carry the daemon reason, `code` only when the result had one.
|
|
87
|
+
* The plain deploy failure shares the CODE (`errorType: 'worker_replaced'`, the
|
|
88
|
+
* turn really ended), so the code alone must never qualify, and a code that
|
|
89
|
+
* is present but different disqualifies.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isDaemonHandoverFailure(event: Record<string, unknown> | null | undefined): boolean;
|
|
92
|
+
/** What the durable record says about a handed-over turn, read while waiting. */
|
|
93
|
+
export type HandoverReading =
|
|
94
|
+
/** Still failed with the hand-over reason, or not yet written: keep waiting. */
|
|
95
|
+
{
|
|
96
|
+
state: 'pending';
|
|
97
|
+
}
|
|
98
|
+
/** The machine finished and the server repaired the row. */
|
|
99
|
+
| {
|
|
100
|
+
state: 'repaired';
|
|
101
|
+
text: string;
|
|
102
|
+
repairedAt?: string;
|
|
103
|
+
}
|
|
104
|
+
/** The machine's turn ended without finishing, and the server settled it so. */
|
|
105
|
+
| {
|
|
106
|
+
state: 'failed';
|
|
107
|
+
error: string;
|
|
108
|
+
text?: string;
|
|
109
|
+
};
|
|
110
|
+
export type HandoverWaitOptions = {
|
|
111
|
+
/** Read the persisted response message. A throw or null is "no evidence". */
|
|
112
|
+
read: () => Promise<HandoverReading | null>;
|
|
113
|
+
/** The response message the repair notification names. */
|
|
114
|
+
responseMessageId?: string;
|
|
115
|
+
conversationId?: string;
|
|
116
|
+
/** How long to wait for the machine's reply. 0 disables the wait. */
|
|
117
|
+
maxWaitMs: number;
|
|
118
|
+
/** Fallback re-read cadence when no notification arrives. Default 15s. */
|
|
119
|
+
pollIntervalMs?: number;
|
|
120
|
+
/**
|
|
121
|
+
* Where the human status line goes when stdout is reserved for the final
|
|
122
|
+
* result (`--no-stream`). Default stderr.
|
|
123
|
+
*/
|
|
124
|
+
statusOutput?: NodeJS.WritableStream;
|
|
48
125
|
};
|
|
126
|
+
/** Default fallback re-read cadence while waiting on a handed-over turn. */
|
|
127
|
+
export declare const HANDOVER_POLL_INTERVAL_MS = 15000;
|
|
128
|
+
/** `600000` → `10m`, `90000` → `1m30s`, `45000` → `45s`. */
|
|
129
|
+
export declare function formatWaitDuration(ms: number): string;
|
|
49
130
|
/**
|
|
50
131
|
* Write exactly one complete JSON value on one line.
|
|
51
132
|
*
|
|
@@ -106,6 +187,12 @@ export type AgenticStreamOptions = {
|
|
|
106
187
|
* owns that stream and its contract does not include diagnostics.
|
|
107
188
|
*/
|
|
108
189
|
debug?: (line: string) => void;
|
|
190
|
+
/**
|
|
191
|
+
* OSK-13543 — wait for a handed-over local-daemon turn instead of ending on
|
|
192
|
+
* its failure. See {@link isDaemonHandoverFailure}. Omitted: today's
|
|
193
|
+
* behaviour, every `execution:error` settles the run at once.
|
|
194
|
+
*/
|
|
195
|
+
awaitHandover?: HandoverWaitOptions;
|
|
109
196
|
/** Silence before the first durable-record probe. Default 20s. */
|
|
110
197
|
idleTerminalProbeMs?: number;
|
|
111
198
|
/** How long to keep trying to attach before reporting a detach. Default 30s. */
|
|
@@ -493,6 +580,11 @@ export declare class AgenticStreamClient {
|
|
|
493
580
|
* a reconciled detach report the real outcome instead of the detach.
|
|
494
581
|
*/
|
|
495
582
|
private terminalResult;
|
|
583
|
+
/**
|
|
584
|
+
* A handed-over turn being waited on (OSK-13543): the failure event that
|
|
585
|
+
* started the wait, and the timers bounding it. Null otherwise.
|
|
586
|
+
*/
|
|
587
|
+
private handover;
|
|
496
588
|
private resolve;
|
|
497
589
|
private reject;
|
|
498
590
|
/** The terminal result, or null while the run is still in flight. */
|
|
@@ -542,6 +634,44 @@ export declare class AgenticStreamClient {
|
|
|
542
634
|
probeTerminal(reason: string): Promise<boolean>;
|
|
543
635
|
cancel(reason?: string): void;
|
|
544
636
|
close(): void;
|
|
637
|
+
/**
|
|
638
|
+
* True while a handed-over turn is being waited on (OSK-13543). A Ctrl-C in
|
|
639
|
+
* this state stops the wait; it cannot cancel the machine's turn.
|
|
640
|
+
*/
|
|
641
|
+
awaitingHandover(): boolean;
|
|
642
|
+
/**
|
|
643
|
+
* OSK-13543 — the run failed because a deploy replaced the server relaying a
|
|
644
|
+
* turn the machine is still running. That failure is the server's PROVISIONAL
|
|
645
|
+
* verdict: it keeps the conversation held and rewrites the row from the
|
|
646
|
+
* machine's own turn-end. Ending here told a CLI user "failed" about a turn
|
|
647
|
+
* whose answer the web showed ninety seconds later.
|
|
648
|
+
*
|
|
649
|
+
* So keep the stream, the user-room subscription and the envelope replay
|
|
650
|
+
* open, and settle on what the RECORD converges to: repaired → completed,
|
|
651
|
+
* settled failed → failed with the machine's reason, neither within the
|
|
652
|
+
* bound → the original failure. The record, not the notification, decides:
|
|
653
|
+
* a notification only says "read again", so a lost or duplicated one costs
|
|
654
|
+
* at most a poll interval.
|
|
655
|
+
*
|
|
656
|
+
* Returns false (and changes nothing) when the run cannot wait: no hand-over
|
|
657
|
+
* reader configured, a zero bound, or a run already settling.
|
|
658
|
+
*/
|
|
659
|
+
private beginHandoverWait;
|
|
660
|
+
/** Ask the record again; coalesces with a read already in flight. */
|
|
661
|
+
private requestHandoverRead;
|
|
662
|
+
/** One read of the record. True when it settled the run. */
|
|
663
|
+
private readHandover;
|
|
664
|
+
/** The bound ran out: one last look, then the original failure. */
|
|
665
|
+
private expireHandover;
|
|
666
|
+
private stopHandoverTimers;
|
|
667
|
+
private endHandover;
|
|
668
|
+
/**
|
|
669
|
+
* The server's "this row was rewritten" notice (`lateDeliveryRepair`
|
|
670
|
+
* `notifyViewersMessageRewritten`), delivered to every socket of the user
|
|
671
|
+
* through the `user:<id>` room this socket joins on connect. A hint to read,
|
|
672
|
+
* never a verdict.
|
|
673
|
+
*/
|
|
674
|
+
private onConversationUpdate;
|
|
545
675
|
private debug;
|
|
546
676
|
/**
|
|
547
677
|
* Record a hole being passed over — the moment replay has given up on it.
|
|
@@ -622,6 +752,11 @@ export declare class AgenticStreamClient {
|
|
|
622
752
|
*/
|
|
623
753
|
private emitToolEvent;
|
|
624
754
|
private emit;
|
|
755
|
+
/**
|
|
756
|
+
* Human-mode lines for the hand-over wait. Returns true when the event is
|
|
757
|
+
* fully rendered here; false lets the ordinary rendering continue.
|
|
758
|
+
*/
|
|
759
|
+
private renderHandoverStatus;
|
|
625
760
|
private finish;
|
|
626
761
|
private finishAfterReplay;
|
|
627
762
|
/**
|