@skrr-ai/cli 0.1.50 → 0.1.51
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.js +59 -2
- package/dist/commands/agents/chat.d.ts +14 -0
- package/dist/commands/agents/chat.js +37 -1
- package/dist/commands/agents/show.d.ts +9 -0
- package/dist/commands/agents/show.js +53 -0
- package/dist/commands/agents/update.d.ts +9 -0
- package/dist/commands/agents/update.js +27 -0
- package/dist/commands/balance/show.d.ts +34 -0
- package/dist/commands/balance/show.js +92 -2
- package/dist/commands/commitments/doctor.d.ts +1 -0
- package/dist/commands/commitments/doctor.js +19 -4
- package/dist/commands/commitments/effective-policy.js +15 -1
- package/dist/commands/commitments/next-tick.js +3 -1
- package/dist/commands/commitments/preflight.js +10 -0
- package/dist/commands/commitments/show.js +11 -1
- package/dist/commands/daemon/config.d.ts +15 -0
- package/dist/commands/daemon/config.js +38 -0
- package/dist/commands/daemon/index.js +1 -0
- package/dist/commands/daemon/status.js +15 -1
- package/dist/commands/harnesses/models.d.ts +54 -0
- package/dist/commands/harnesses/models.js +100 -7
- package/dist/commands/harnesses/show.d.ts +11 -0
- package/dist/commands/harnesses/show.js +35 -0
- package/dist/commands/harnesses/update.d.ts +28 -1
- package/dist/commands/harnesses/update.js +73 -10
- package/dist/commands/outbox/list.d.ts +12 -1
- package/dist/commands/outbox/list.js +31 -2
- package/dist/commands/revoke-daemon.js +9 -0
- package/dist/commands/spaces/create.d.ts +1 -0
- package/dist/commands/spaces/create.js +17 -0
- package/dist/commands/spaces/show.js +5 -0
- package/dist/commands/spaces/update.d.ts +1 -0
- package/dist/commands/spaces/update.js +16 -0
- package/dist/commands/tasks/create.d.ts +1 -0
- package/dist/commands/tasks/create.js +5 -0
- package/dist/commands/tasks/show.js +45 -0
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +21 -0
- package/dist/commands/tasks/upsert.d.ts +1 -0
- package/dist/commands/tasks/upsert.js +16 -0
- package/dist/lib/agentic-stream.d.ts +68 -1
- package/dist/lib/agentic-stream.js +294 -7
- package/dist/lib/auth-core-init.d.ts +2 -10
- package/dist/lib/auth-core-init.js +20 -67
- package/dist/lib/auth-failure.d.ts +33 -0
- package/dist/lib/auth-failure.js +65 -0
- package/dist/lib/commitments.d.ts +21 -1
- package/dist/lib/commitments.js +34 -0
- package/dist/lib/daemonBroker.d.ts +14 -0
- package/dist/lib/daemonBroker.js +64 -12
- package/dist/lib/delegated-cli.js +4 -1
- package/dist/lib/exec-runtime-binary.d.ts +3 -1
- package/dist/lib/exec-runtime-binary.js +4 -2
- package/dist/lib/first-party-harness-broker.js +4 -1
- package/dist/lib/harnesses.d.ts +10 -0
- package/dist/lib/live-runtime-binary.d.ts +40 -0
- package/dist/lib/live-runtime-binary.js +193 -0
- package/dist/lib/login.d.ts +23 -0
- package/dist/lib/login.js +44 -10
- package/dist/lib/message-intent.js +3 -1
- package/dist/lib/tasks.d.ts +20 -0
- package/dist/lib/tasks.js +54 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +1928 -1924
- package/oclif.manifest.json +23832 -23688
- package/package.json +1 -1
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queuedBecause = queuedBecause;
|
|
3
4
|
exports.outboxListSnapshot = outboxListSnapshot;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const keychain_1 = require("../../lib/keychain");
|
|
7
8
|
const format_1 = require("../../lib/format");
|
|
8
9
|
const outbox_1 = require("../../lib/outbox");
|
|
10
|
+
/**
|
|
11
|
+
* Why one entry was queued, in words (OSK-12121). The queue holds more than
|
|
12
|
+
* outage casualties — a 401 is queued too, so authored content survives a lapsed
|
|
13
|
+
* sign-in — and a list that described every entry as "the API was unavailable"
|
|
14
|
+
* sent a signed-out reader to wait for an outage that was not happening.
|
|
15
|
+
*/
|
|
16
|
+
function queuedBecause(entry) {
|
|
17
|
+
const status = entry.reason?.status;
|
|
18
|
+
if (status === 401)
|
|
19
|
+
return { label: 'sign-in refused (401)', kind: 'auth' };
|
|
20
|
+
if (status === 429)
|
|
21
|
+
return { label: 'throttled (429)', kind: 'throttled' };
|
|
22
|
+
if ((typeof status === 'number' && status >= 500) || (0, outbox_1.isOutageFailure)(entry.reason)) {
|
|
23
|
+
return { label: status ? `unavailable (${status})` : 'unreachable', kind: 'unavailable' };
|
|
24
|
+
}
|
|
25
|
+
return { label: status ? `HTTP ${status}` : 'unknown', kind: 'other' };
|
|
26
|
+
}
|
|
9
27
|
function outboxListSnapshot(profile) {
|
|
10
28
|
const queued = (0, outbox_1.listWrites)(profile);
|
|
11
29
|
const incident = (0, outbox_1.readIncident)(profile);
|
|
@@ -24,7 +42,7 @@ function outboxListSnapshot(profile) {
|
|
|
24
42
|
* where they find out.
|
|
25
43
|
*/
|
|
26
44
|
class OutboxList extends base_command_1.BaseCommand {
|
|
27
|
-
static description = 'Show writes queued locally because the
|
|
45
|
+
static description = 'Show writes queued locally because the server could not take them: an outage, throttling, or a lapsed sign-in';
|
|
28
46
|
static examples = [
|
|
29
47
|
'<%= config.bin %> outbox list',
|
|
30
48
|
'<%= config.bin %> outbox list --json',
|
|
@@ -46,7 +64,9 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
46
64
|
`${incident.consecutive} consecutive failure(s).`);
|
|
47
65
|
this.log('');
|
|
48
66
|
}
|
|
49
|
-
else if (incident) {
|
|
67
|
+
else if (incident && entries.some((e) => queuedBecause(e).kind === 'unavailable')) {
|
|
68
|
+
// A past incident is context only for entries an outage put here. Beside
|
|
69
|
+
// a queue of sign-in refusals it reads as the explanation, and is not.
|
|
50
70
|
this.log(`Last service incident: ${incident.baseURL} failed at ${incident.lastOutageAt}` +
|
|
51
71
|
`${incident.status ? ` (HTTP ${incident.status})` : ''}. ` +
|
|
52
72
|
'No current outage is confirmed.');
|
|
@@ -61,6 +81,7 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
61
81
|
queued: e.queuedAt,
|
|
62
82
|
command: e.command,
|
|
63
83
|
summary: e.summary,
|
|
84
|
+
why: queuedBecause(e).label,
|
|
64
85
|
// Surface the attempt count only once it matters; a column of "0"s is
|
|
65
86
|
// noise, but an entry nearing the cap needs a human.
|
|
66
87
|
attempts: e.attempts > 0 ? `${e.attempts}/${outbox_1.MAX_REPLAY_ATTEMPTS}` : '-',
|
|
@@ -69,6 +90,7 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
69
90
|
{ key: 'queued', header: 'QUEUED', maxWidth: 24 },
|
|
70
91
|
{ key: 'command', header: 'COMMAND', maxWidth: 24 },
|
|
71
92
|
{ key: 'summary', header: 'SUMMARY', maxWidth: 40 },
|
|
93
|
+
{ key: 'why', header: 'WHY', maxWidth: 24 },
|
|
72
94
|
{ key: 'attempts', header: 'TRIES' },
|
|
73
95
|
], (line) => this.log(line));
|
|
74
96
|
const exhausted = entries.filter((e) => e.attempts >= outbox_1.MAX_REPLAY_ATTEMPTS);
|
|
@@ -77,6 +99,13 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
77
99
|
this.log(`${exhausted.length} entry(ies) hit the ${outbox_1.MAX_REPLAY_ATTEMPTS}-attempt cap and will not be ` +
|
|
78
100
|
'retried automatically. Inspect the failure, then retry or drop them explicitly.');
|
|
79
101
|
}
|
|
102
|
+
const authQueued = entries.filter((e) => queuedBecause(e).kind === 'auth').length;
|
|
103
|
+
if (authQueued > 0) {
|
|
104
|
+
this.log('');
|
|
105
|
+
this.log(`${authQueued} entry(ies) were queued because the server refused this sign-in, not because ` +
|
|
106
|
+
`it was down. Sign in with \`${this.config.bin} login\` first; a replay before that is ` +
|
|
107
|
+
'refused the same way.');
|
|
108
|
+
}
|
|
80
109
|
this.log('');
|
|
81
110
|
this.log(`Replay: ${this.config.bin} outbox retry`);
|
|
82
111
|
this.log(`Discard: ${this.config.bin} outbox drop <id>`);
|
|
@@ -67,6 +67,8 @@ class RevokeDaemon extends base_command_1.BaseCommand {
|
|
|
67
67
|
const host = resolvedDaemon?.machineUuid
|
|
68
68
|
? resolvedDaemon.machineUuid.slice(0, 12)
|
|
69
69
|
: (resolvedDaemon?.os ?? 'unknown host');
|
|
70
|
+
this.log('Revoking a daemon also revokes every CLI sign-in it brokered — on its machine, ' +
|
|
71
|
+
`\`${this.config.bin} login\` without a browser signs in through the daemon.`);
|
|
70
72
|
const confirmed = await this.confirm(`Revoke daemon "${name}" on ${host}? [y/N] `);
|
|
71
73
|
if (!confirmed) {
|
|
72
74
|
this.log('Aborted.');
|
|
@@ -99,6 +101,13 @@ class RevokeDaemon extends base_command_1.BaseCommand {
|
|
|
99
101
|
const daemonId = (res.daemon?.id ?? resolvedId).slice(0, 12);
|
|
100
102
|
if (res.revoked) {
|
|
101
103
|
this.log(`Revoked daemon ${daemonName} (${daemonId}).`);
|
|
104
|
+
if (res.callerSessionRevoked === true) {
|
|
105
|
+
// Said now, not discovered later as a refresh failure: the sign-in this
|
|
106
|
+
// command used was brokered by that daemon and died with it.
|
|
107
|
+
this.warn(`This also signed out ${this.config.bin} on this machine: its sign-in was brokered ` +
|
|
108
|
+
`by that daemon and was revoked with it. Run \`${this.config.bin} daemon login\` to ` +
|
|
109
|
+
`re-enroll the daemon, then \`${this.config.bin} login\` to sign the CLI in again.`);
|
|
110
|
+
}
|
|
102
111
|
// exit 0 (default)
|
|
103
112
|
}
|
|
104
113
|
else if (res.alreadyRevoked) {
|
|
@@ -19,6 +19,7 @@ export default class SpacesCreate extends BaseCommand {
|
|
|
19
19
|
'group-chat': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
20
|
parent: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
21
|
'inherit-access': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
23
|
};
|
|
23
24
|
run(): Promise<void>;
|
|
24
25
|
}
|
|
@@ -9,6 +9,7 @@ const label_ref_1 = require("../../lib/label-ref");
|
|
|
9
9
|
const web_url_1 = require("../../lib/web-url");
|
|
10
10
|
const prompt_1 = require("../../lib/prompt");
|
|
11
11
|
const created_line_1 = require("../../lib/created-line");
|
|
12
|
+
const tasks_1 = require("../../lib/tasks");
|
|
12
13
|
class SpacesCreate extends base_command_1.BaseCommand {
|
|
13
14
|
static description = 'Create a new space';
|
|
14
15
|
static examples = [
|
|
@@ -51,6 +52,11 @@ class SpacesCreate extends base_command_1.BaseCommand {
|
|
|
51
52
|
allowNo: true,
|
|
52
53
|
description: 'Inherit members/permissions from the parent chain (default). Use --no-inherit-access to make a private subspace.',
|
|
53
54
|
}),
|
|
55
|
+
'working-directory': core_1.Flags.string({
|
|
56
|
+
description: "Space-level working directory for this space's local Harness runs — an absolute " +
|
|
57
|
+
"path on the executing daemon's filesystem. The middle layer of task → space → " +
|
|
58
|
+
'agent: a per-task override wins, the agent default applies when neither is set.',
|
|
59
|
+
}),
|
|
54
60
|
};
|
|
55
61
|
async run() {
|
|
56
62
|
this.requireAuth();
|
|
@@ -95,7 +101,18 @@ class SpacesCreate extends base_command_1.BaseCommand {
|
|
|
95
101
|
groupChatId: flags['group-chat'],
|
|
96
102
|
parentSpaceId: flags.parent,
|
|
97
103
|
inheritAccess: flags['inherit-access'],
|
|
104
|
+
workingDirectory: (0, tasks_1.workingDirectoryFromFlag)(flags['working-directory']),
|
|
98
105
|
});
|
|
106
|
+
// Post-merge, so --working-directory and --from-json are checked
|
|
107
|
+
// identically: a relative path is refused here rather than stored and
|
|
108
|
+
// silently fallen back from at run time on the daemon.
|
|
109
|
+
const workingDirectoryError = (0, tasks_1.workingDirectoryValidationError)(payload.workingDirectory);
|
|
110
|
+
if (workingDirectoryError) {
|
|
111
|
+
this.error(workingDirectoryError, { exit: 2 });
|
|
112
|
+
}
|
|
113
|
+
// 'none' on create means "no space default" — the field is absent anyway.
|
|
114
|
+
if (payload.workingDirectory === null)
|
|
115
|
+
delete payload.workingDirectory;
|
|
99
116
|
if (!payload.title || typeof payload.title !== 'string') {
|
|
100
117
|
this.error('A title is required. Pass --title "..." or include it in --from-json.', {
|
|
101
118
|
exit: 1,
|
|
@@ -57,6 +57,11 @@ class SpacesShow extends base_command_1.BaseCommand {
|
|
|
57
57
|
this.log(`Leader: ${s.leadAgentId ?? '-'}`);
|
|
58
58
|
this.log(`Target Date: ${s.targetDate ?? '-'}`);
|
|
59
59
|
this.log(`Goals: ${Array.isArray(s.goalIds) ? s.goalIds.length : 0}`);
|
|
60
|
+
// Only when set — the space default is the middle layer of the
|
|
61
|
+
// task → space → agent cascade, and most spaces leave it unset.
|
|
62
|
+
if (typeof s.workingDirectory === 'string' && s.workingDirectory) {
|
|
63
|
+
this.log(`Directory: ${s.workingDirectory}`);
|
|
64
|
+
}
|
|
60
65
|
this.log(`Archived: ${s.archivedAt ? 'yes' : 'no'}`);
|
|
61
66
|
this.log(`Parent: ${s.parentSpaceId ?? '(root)'}`);
|
|
62
67
|
this.log(`Depth: ${s.depth ?? 0}`);
|
|
@@ -30,6 +30,7 @@ export default class SpacesUpdate extends BaseCommand {
|
|
|
30
30
|
'auto-start-on-backlog-exit': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
31
31
|
'triage-external-intake': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
32
32
|
'default-task-template': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
33
|
+
'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
33
34
|
};
|
|
34
35
|
run(): Promise<void>;
|
|
35
36
|
}
|
|
@@ -135,6 +135,12 @@ class SpacesUpdate extends base_command_1.BaseCommand {
|
|
|
135
135
|
description: 'Default task template for new tasks in this space — a template id, reference, or ' +
|
|
136
136
|
'exact title (the server resolves it to the template). Pass "none" to clear.',
|
|
137
137
|
}),
|
|
138
|
+
'working-directory': core_1.Flags.string({
|
|
139
|
+
description: "Space-level working directory for this space's local Harness runs — an absolute " +
|
|
140
|
+
"path on the executing daemon's filesystem. The middle layer of task → space → " +
|
|
141
|
+
'agent: a per-task override wins, the agent default applies when neither is set. ' +
|
|
142
|
+
'Pass "none" to clear.',
|
|
143
|
+
}),
|
|
138
144
|
};
|
|
139
145
|
async run() {
|
|
140
146
|
this.requireAuth();
|
|
@@ -159,11 +165,21 @@ class SpacesUpdate extends base_command_1.BaseCommand {
|
|
|
159
165
|
autoStartOnBacklogExit: flags['auto-start-on-backlog-exit'],
|
|
160
166
|
triageExternalIntake: flags['triage-external-intake'],
|
|
161
167
|
defaultTaskTemplateId: clearable(flags['default-task-template']),
|
|
168
|
+
// Per-space working-directory default. 'none'/'clear'/'null' ⇒ null
|
|
169
|
+
// ($unset → resolution falls through to the agent default).
|
|
170
|
+
workingDirectory: (0, tasks_1.workingDirectoryFromFlag)(flags['working-directory']),
|
|
162
171
|
estimateScale: flags['estimate-scale'],
|
|
163
172
|
estimateScaleMigration: flags['estimate-migration'],
|
|
164
173
|
defaultEstimate: flags['default-estimate'],
|
|
165
174
|
useDefaultEstimateForUnestimated: flags['use-default-estimate'],
|
|
166
175
|
});
|
|
176
|
+
// Post-merge, so --working-directory and --from-json are checked
|
|
177
|
+
// identically. A relative path stores fine and then silently falls back on
|
|
178
|
+
// the daemon at run time — refuse it here, where the mistake can be named.
|
|
179
|
+
const workingDirectoryError = (0, tasks_1.workingDirectoryValidationError)(payload.workingDirectory);
|
|
180
|
+
if (workingDirectoryError) {
|
|
181
|
+
this.error(workingDirectoryError, { exit: 2 });
|
|
182
|
+
}
|
|
167
183
|
// Worktree-isolation default lives on the space's deliveryPolicy subdoc,
|
|
168
184
|
// which the API replaces wholesale on update. Merge into the policy the user
|
|
169
185
|
// supplied via --from-json, else the space's current policy, so setting just
|
|
@@ -65,6 +65,7 @@ export default class TasksCreate extends BaseCommand {
|
|
|
65
65
|
estimate: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
66
66
|
tag: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
67
67
|
'label-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
68
|
+
'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
68
69
|
worktree: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
69
70
|
'lifecycle-definition': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
70
71
|
'lifecycle-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -226,6 +226,11 @@ class TasksCreate extends base_command_1.BaseCommand {
|
|
|
226
226
|
'An unknown name is refused, never created. Manage labels with `skrr labels`.',
|
|
227
227
|
multiple: true,
|
|
228
228
|
}),
|
|
229
|
+
'working-directory': core_1.Flags.string({
|
|
230
|
+
description: "Working directory for this task's local Harness runs — an absolute path on " +
|
|
231
|
+
"the daemon's filesystem. Overrides the agent's default; omit to inherit the " +
|
|
232
|
+
'agent default, then the daemon session default.',
|
|
233
|
+
}),
|
|
229
234
|
worktree: core_1.Flags.boolean({
|
|
230
235
|
allowNo: true,
|
|
231
236
|
description: "Isolate this task's autonomous runs in a one-shot git worktree (--worktree), " +
|
|
@@ -402,6 +402,51 @@ class TasksShow extends base_command_1.BaseCommand {
|
|
|
402
402
|
this.log(`Result: ${priorLine}`);
|
|
403
403
|
}
|
|
404
404
|
this.log(`Executor: ${taskExecutorLabel(t, this.config.bin)}`);
|
|
405
|
+
// Effective run directory, resolved the same way the server resolves it —
|
|
406
|
+
// most specific scope wins: task → space → agent. The daemon layers below
|
|
407
|
+
// (session.defaultCwd → --cwd) are the daemon's own and cannot be read
|
|
408
|
+
// here, so when no owned layer wins the row is omitted rather than
|
|
409
|
+
// labelled "inherited". Two extra reads, paid only when the row can say
|
|
410
|
+
// something (a detail view).
|
|
411
|
+
if (typeof t.workingDirectory === 'string' && t.workingDirectory) {
|
|
412
|
+
this.log(`Directory: ${t.workingDirectory} (task)`);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
let spaceDir = null;
|
|
416
|
+
let agentDir = null;
|
|
417
|
+
if (typeof t.spaceId === 'string' && t.spaceId) {
|
|
418
|
+
try {
|
|
419
|
+
const space = (await data_provider_1.dataService.getSpace(t.spaceId));
|
|
420
|
+
if (typeof space?.workingDirectory === 'string' && space.workingDirectory) {
|
|
421
|
+
spaceDir = space.workingDirectory;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
// Unreadable space → no claim, not a wrong one.
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (!spaceDir) {
|
|
429
|
+
const agentRef = (typeof t.driAgentId === 'string' && t.driAgentId) ||
|
|
430
|
+
(Array.isArray(t.assigneeAgentIds) ? t.assigneeAgentIds[0] : null);
|
|
431
|
+
if (agentRef) {
|
|
432
|
+
try {
|
|
433
|
+
const agentRow = (await data_provider_1.dataService.getAgentById({
|
|
434
|
+
agent_id: agentRef,
|
|
435
|
+
}));
|
|
436
|
+
if (typeof agentRow?.workingDirectory === 'string' && agentRow.workingDirectory) {
|
|
437
|
+
agentDir = agentRow.workingDirectory;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// Same rule: unreadable → silence.
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const inheritedDir = spaceDir ?? agentDir;
|
|
446
|
+
if (inheritedDir) {
|
|
447
|
+
this.log(`Directory: ${inheritedDir} (${spaceDir ? 'space' : 'agent'} default)`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
405
450
|
this.log(`Priority: ${t.priority ?? '-'}`);
|
|
406
451
|
this.log(`Parent: ${t.parentId ?? '-'}`);
|
|
407
452
|
this.log(`Space: ${t.spaceId ?? '-'}`);
|
|
@@ -58,6 +58,7 @@ export default class TasksUpdate extends BaseCommand {
|
|
|
58
58
|
tag: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
59
59
|
'label-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
60
60
|
'repair-release-pins': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
61
|
+
'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
61
62
|
worktree: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
62
63
|
};
|
|
63
64
|
run(): Promise<void>;
|
|
@@ -168,6 +168,11 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
168
168
|
'rejected with AGENT_RELEASE_PIN_STATE_INVALID: pins that govern no current ' +
|
|
169
169
|
'assignee are dropped and broken ones are re-resolved (recorded in the activity log).',
|
|
170
170
|
}),
|
|
171
|
+
'working-directory': core_1.Flags.string({
|
|
172
|
+
description: "Working directory for this task's local Harness runs — an absolute path on " +
|
|
173
|
+
'the daemon\'s filesystem. Overrides the agent\'s default. Pass "none" to ' +
|
|
174
|
+
'clear back to inheriting the agent default.',
|
|
175
|
+
}),
|
|
171
176
|
worktree: core_1.Flags.boolean({
|
|
172
177
|
allowNo: true,
|
|
173
178
|
description: "Isolate this task's autonomous runs in a git worktree (--worktree) or force " +
|
|
@@ -348,6 +353,10 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
348
353
|
// Worktree isolation override. --worktree ⇒ 'ephemeral', --no-worktree ⇒
|
|
349
354
|
// 'shared', omitted ⇒ unchanged. (Clear-to-inherit via --from-json.)
|
|
350
355
|
executionIsolation: (0, tasks_2.executionIsolationFromWorktreeFlag)(flags.worktree),
|
|
356
|
+
// Per-task working-directory override. --working-directory <abs path> ⇒
|
|
357
|
+
// set; "none"/"clear"/"null" ⇒ null (clears → inherit the agent
|
|
358
|
+
// default); omitted ⇒ unchanged.
|
|
359
|
+
workingDirectory: (0, tasks_2.workingDirectoryFromFlag)(flags['working-directory']),
|
|
351
360
|
// A mutation option, not a field — `undefined` unless explicitly asked
|
|
352
361
|
// for, so it never counts toward the "nothing to update" check below.
|
|
353
362
|
repairReleasePins: flags['repair-release-pins'] ? true : undefined,
|
|
@@ -361,6 +370,18 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
361
370
|
retryable: false,
|
|
362
371
|
});
|
|
363
372
|
}
|
|
373
|
+
// Post-merge, so flag and --from-json inputs are checked identically. A
|
|
374
|
+
// relative path stores fine and then silently falls back at run time —
|
|
375
|
+
// refuse it here, where the mistake can still be named.
|
|
376
|
+
const workingDirectoryError = (0, tasks_2.workingDirectoryValidationError)(payload.workingDirectory);
|
|
377
|
+
if (workingDirectoryError) {
|
|
378
|
+
this.failWithCliError({
|
|
379
|
+
message: workingDirectoryError,
|
|
380
|
+
code: 'INVALID_TASK_WORKING_DIRECTORY',
|
|
381
|
+
exit: 2,
|
|
382
|
+
retryable: false,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
364
385
|
if (payloadKeys.length === 0) {
|
|
365
386
|
this.failWithCliError({
|
|
366
387
|
message: 'Nothing to update. Pass at least one flag (e.g. --title) or --from-json.',
|
|
@@ -20,6 +20,7 @@ export default class TasksUpsert extends BaseCommand {
|
|
|
20
20
|
'assignee-user': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
21
|
'execution-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
22
|
'due-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
|
+
'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
24
|
tag: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
25
|
};
|
|
25
26
|
run(): Promise<void>;
|
|
@@ -57,6 +57,10 @@ class TasksUpsert extends base_command_1.BaseCommand {
|
|
|
57
57
|
options: ['agent', 'human'],
|
|
58
58
|
}),
|
|
59
59
|
'due-date': core_1.Flags.string({ description: 'Due date (ISO string)' }),
|
|
60
|
+
'working-directory': core_1.Flags.string({
|
|
61
|
+
description: "Working directory for this task's local Harness runs — an absolute path on " +
|
|
62
|
+
'the daemon\'s filesystem. Pass "none" to clear an existing override.',
|
|
63
|
+
}),
|
|
60
64
|
tag: core_1.Flags.string({ description: 'Replace/create tag (repeatable)', multiple: true }),
|
|
61
65
|
};
|
|
62
66
|
async run() {
|
|
@@ -75,6 +79,18 @@ class TasksUpsert extends base_command_1.BaseCommand {
|
|
|
75
79
|
});
|
|
76
80
|
}
|
|
77
81
|
const payload = (0, tasks_1.buildTaskCreatePayload)(body, flags);
|
|
82
|
+
// Checked here, not inside validateTaskCreatePayload's create-leg call —
|
|
83
|
+
// an upsert that resolves to UPDATE never reaches that validator, and a
|
|
84
|
+
// relative path would silently no-op at run time on the daemon.
|
|
85
|
+
const workingDirectoryError = (0, tasks_1.workingDirectoryValidationError)(payload.workingDirectory);
|
|
86
|
+
if (workingDirectoryError) {
|
|
87
|
+
this.failWithCliError({
|
|
88
|
+
message: workingDirectoryError,
|
|
89
|
+
code: 'INVALID_TASK_WORKING_DIRECTORY',
|
|
90
|
+
exit: 2,
|
|
91
|
+
retryable: false,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
78
94
|
let scope;
|
|
79
95
|
try {
|
|
80
96
|
scope = await (0, tasks_1.resolveTaskCreateScope)(payload);
|
|
@@ -94,6 +94,20 @@ export type AgenticStreamOptions = {
|
|
|
94
94
|
/** How long to keep trying to attach before reporting a detach. Default 30s. */
|
|
95
95
|
attachTimeoutMs?: number;
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* What a permission request would DO, in one line a person can judge.
|
|
99
|
+
*
|
|
100
|
+
* The prompt used to be `Allow tool <name>?` and nothing else, so a shell
|
|
101
|
+
* approval read "Allow tool unknown? [y/N]" and a person approved a command
|
|
102
|
+
* they never saw (OSK-12119). The command, or else the path, or else the
|
|
103
|
+
* arguments, is the part that decides the answer.
|
|
104
|
+
*
|
|
105
|
+
* The text is chosen by the agent and printed to a terminal, so control
|
|
106
|
+
* characters are escaped: an embedded carriage return or escape sequence could
|
|
107
|
+
* otherwise redraw the line and show the person a different command than the
|
|
108
|
+
* one they are approving.
|
|
109
|
+
*/
|
|
110
|
+
export declare function describePermissionTarget(toolInput: unknown): string | null;
|
|
97
111
|
/**
|
|
98
112
|
* Small Node-side adapter for the existing Agentic Socket.IO contract.
|
|
99
113
|
* It deliberately normalizes only lifecycle/text/tool events; the server's
|
|
@@ -159,6 +173,25 @@ export type ProjectedToolEvent = {
|
|
|
159
173
|
* of the run. The pairing is unambiguous within a turn.
|
|
160
174
|
*/
|
|
161
175
|
export declare function projectDaemonToolEvents(envelopes: unknown[], namesByCall: Map<string, string>): ProjectedToolEvent[];
|
|
176
|
+
/**
|
|
177
|
+
* WHO or WHAT decided a permission request, as a reader needs it (OSK-12113).
|
|
178
|
+
*
|
|
179
|
+
* `by` names the surface or fallback that resolved the ask and is a closed wire
|
|
180
|
+
* enum; `basis` beside it says what decided. The distinction is the finding: an
|
|
181
|
+
* edit acceptEdits applied on its own was recorded `by: "cli"`, which read as
|
|
182
|
+
* the CLI user having approved something nobody was asked about.
|
|
183
|
+
*/
|
|
184
|
+
export declare function describePermissionDecider(ev: {
|
|
185
|
+
by?: unknown;
|
|
186
|
+
basis?: unknown;
|
|
187
|
+
mode?: unknown;
|
|
188
|
+
surface?: unknown;
|
|
189
|
+
}): string;
|
|
190
|
+
/**
|
|
191
|
+
* Daemon `permission-resolved` envelopes as `permission.resolved` events, named
|
|
192
|
+
* by the tool their `permission-ask` announced and carrying who decided.
|
|
193
|
+
*/
|
|
194
|
+
export declare function projectPermissionResolutions(envelopes: unknown[], toolsByRequest: Map<string, string>): AgenticStreamEvent[];
|
|
162
195
|
/**
|
|
163
196
|
* The part of a cumulative snapshot that has not been shown yet.
|
|
164
197
|
*
|
|
@@ -262,8 +295,32 @@ export type SeqGap = {
|
|
|
262
295
|
from: number;
|
|
263
296
|
to: number;
|
|
264
297
|
};
|
|
298
|
+
/**
|
|
299
|
+
* `service` envelopes that carry a `notice` — words for the person, not
|
|
300
|
+
* diagnostics — projected as `session.notice` events. Today the only notice is
|
|
301
|
+
* `harness-session-reset`: the local harness could not resume its previous
|
|
302
|
+
* session and began a new one, so its local working context is gone.
|
|
303
|
+
*/
|
|
304
|
+
export declare function projectSessionNotices(envelopes: unknown[]): AgenticStreamEvent[];
|
|
265
305
|
export declare function detectSeqGap(highestApplied: number, incoming: number): SeqGap | null;
|
|
266
|
-
|
|
306
|
+
/**
|
|
307
|
+
* The notice a reader sees when chunks went missing.
|
|
308
|
+
*
|
|
309
|
+
* It names `run.result` only when this client will actually fill `run.result`
|
|
310
|
+
* from the persisted message (`recordBackfill`). The sentence used to promise
|
|
311
|
+
* it unconditionally, while `run.result.text` was folded from the envelopes
|
|
312
|
+
* that DID arrive — so the one run told to trust `run.result` was the run whose
|
|
313
|
+
* `run.result.text` was empty, with the answer only in the record (OSK-12132).
|
|
314
|
+
*/
|
|
315
|
+
export declare function seqGapNotice(gap: SeqGap, options?: {
|
|
316
|
+
recordBackfill?: boolean;
|
|
317
|
+
}): string;
|
|
318
|
+
/**
|
|
319
|
+
* The permission mode a `run.started` reports, and where it came from
|
|
320
|
+
* (OSK-12089). `null` when the event carries no mode report at all — a cloud
|
|
321
|
+
* turn from an older server, say — so nothing is claimed.
|
|
322
|
+
*/
|
|
323
|
+
export declare function describeRunPermission(event: AgenticStreamEvent): string | null;
|
|
267
324
|
export declare class AgenticStreamClient {
|
|
268
325
|
private readonly options;
|
|
269
326
|
private socket;
|
|
@@ -291,6 +348,8 @@ export declare class AgenticStreamClient {
|
|
|
291
348
|
private readonly seenLifecycle;
|
|
292
349
|
/** `call` → tool name, so a daemon `tool-call-end` can be named (OSK-7730). */
|
|
293
350
|
private readonly toolNamesByCall;
|
|
351
|
+
/** `permission-ask` requestId → tool name, to name its resolution (OSK-12113). */
|
|
352
|
+
private readonly toolNamesByPermission;
|
|
294
353
|
/** Text already written to the terminal, for suffix-only rendering. */
|
|
295
354
|
private renderedText;
|
|
296
355
|
private idleTimer;
|
|
@@ -372,6 +431,14 @@ export declare class AgenticStreamClient {
|
|
|
372
431
|
private emitToolEvent;
|
|
373
432
|
private emit;
|
|
374
433
|
private finish;
|
|
434
|
+
/**
|
|
435
|
+
* The answer as the durable record holds it, for a run whose stream had a
|
|
436
|
+
* hole. Bounded: the record is written before the terminal event is sent, so
|
|
437
|
+
* it is normally there at once, and a record that stays silent must not hold
|
|
438
|
+
* the run open — the reader then gets what the stream delivered.
|
|
439
|
+
*/
|
|
440
|
+
private readRecordedText;
|
|
441
|
+
private settle;
|
|
375
442
|
/**
|
|
376
443
|
* OSK-2944 — a transient connect failure must not end an ACCEPTED run.
|
|
377
444
|
*
|