@skrr-ai/cli 0.1.37 → 0.1.38
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/dev-fallback.js +167 -4
- package/dist/base-command.js +46 -2
- package/dist/commands/agents/actions/create.js +18 -7
- package/dist/commands/agents/actions/delete.d.ts +1 -0
- package/dist/commands/agents/actions/delete.js +7 -0
- package/dist/commands/agents/actions/update.js +5 -3
- package/dist/commands/agents/api-actions/add.js +5 -2
- package/dist/commands/agents/api-actions/index.d.ts +11 -0
- package/dist/commands/agents/api-actions/index.js +27 -1
- package/dist/commands/agents/avatar/default/index.d.ts +11 -0
- package/dist/commands/agents/avatar/default/index.js +17 -0
- package/dist/commands/agents/avatar/default/set.d.ts +2 -1
- package/dist/commands/agents/avatar/default/set.js +32 -7
- package/dist/commands/agents/avatar/upload.js +7 -1
- package/dist/commands/agents/categories/delete.js +8 -0
- package/dist/commands/agents/categories/index.d.ts +21 -5
- package/dist/commands/agents/categories/index.js +68 -15
- package/dist/commands/agents/chat.d.ts +21 -0
- package/dist/commands/agents/chat.js +94 -7
- package/dist/commands/agents/create.js +11 -4
- package/dist/commands/agents/list.d.ts +15 -0
- package/dist/commands/agents/list.js +31 -1
- package/dist/commands/agents/scene-background/upload.d.ts +30 -0
- package/dist/commands/agents/scene-background/upload.js +97 -0
- package/dist/commands/agents/show.js +13 -0
- package/dist/commands/agents/tools/calls.d.ts +23 -5
- package/dist/commands/agents/tools/calls.js +81 -18
- package/dist/commands/agents/update.js +15 -5
- package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
- package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
- package/dist/commands/convos/messages.d.ts +13 -0
- package/dist/commands/convos/messages.js +40 -16
- package/dist/commands/daemon/index.js +8 -1
- package/dist/commands/daemon/logs.d.ts +26 -0
- package/dist/commands/daemon/logs.js +56 -0
- package/dist/commands/daemon/uninstall.js +1 -1
- package/dist/commands/followups/act.js +3 -2
- package/dist/commands/followups/cancel.d.ts +6 -3
- package/dist/commands/followups/cancel.js +16 -6
- package/dist/commands/followups/observability.d.ts +23 -0
- package/dist/commands/followups/observability.js +114 -0
- package/dist/commands/followups/reschedule.js +1 -1
- package/dist/commands/followups/resolve.js +3 -3
- package/dist/commands/followups/show.js +2 -1
- package/dist/commands/followups/watch.js +9 -5
- package/dist/commands/harnesses/forget.js +28 -0
- package/dist/commands/harnesses/install.d.ts +41 -0
- package/dist/commands/harnesses/install.js +165 -0
- package/dist/commands/harnesses/installers.d.ts +45 -0
- package/dist/commands/harnesses/installers.js +155 -0
- package/dist/commands/harnesses/list.d.ts +82 -1
- package/dist/commands/harnesses/list.js +104 -12
- package/dist/commands/harnesses/models.js +10 -1
- package/dist/commands/harnesses/show.d.ts +15 -0
- package/dist/commands/harnesses/show.js +44 -3
- package/dist/commands/harnesses/update.js +1 -1
- package/dist/commands/harnesses/usage.d.ts +25 -0
- package/dist/commands/harnesses/usage.js +24 -2
- package/dist/commands/instructions/status.js +13 -1
- package/dist/commands/machines/dedicated/catalog.js +4 -15
- package/dist/commands/machines/dedicated/create.d.ts +8 -0
- package/dist/commands/machines/dedicated/create.js +58 -1
- package/dist/commands/machines/dedicated/destroy.js +18 -4
- package/dist/commands/machines/dedicated/list.js +9 -6
- package/dist/commands/machines/hosted/list.js +12 -0
- package/dist/commands/machines/list.js +11 -0
- package/dist/commands/machines/show.d.ts +1 -0
- package/dist/commands/machines/show.js +40 -11
- package/dist/commands/profiles/list.d.ts +23 -0
- package/dist/commands/profiles/list.js +96 -0
- package/dist/commands/search/skills.d.ts +11 -1
- package/dist/commands/search/skills.js +26 -8
- package/dist/commands/skills/details.d.ts +2 -0
- package/dist/commands/skills/details.js +24 -1
- package/dist/commands/skills/import-as-actions.d.ts +32 -0
- package/dist/commands/skills/import-as-actions.js +175 -0
- package/dist/commands/skills/install.d.ts +2 -0
- package/dist/commands/skills/install.js +24 -1
- package/dist/commands/skills/invocable.js +6 -2
- package/dist/commands/skills/local.d.ts +40 -0
- package/dist/commands/skills/local.js +160 -0
- package/dist/commands/skills/search.d.ts +11 -1
- package/dist/commands/skills/search.js +29 -8
- package/dist/commands/spaces/actions/add.js +23 -5
- package/dist/commands/spaces/actions/available.js +3 -0
- package/dist/commands/spaces/actions/list.d.ts +5 -0
- package/dist/commands/spaces/actions/list.js +49 -3
- package/dist/commands/spaces/actions/run.d.ts +28 -0
- package/dist/commands/spaces/actions/run.js +146 -0
- package/dist/commands/spaces/actions/update.js +27 -5
- package/dist/commands/spaces/label-summaries.d.ts +27 -0
- package/dist/commands/spaces/label-summaries.js +64 -0
- package/dist/commands/spaces/task-rollups.d.ts +5 -0
- package/dist/commands/spaces/task-rollups.js +15 -4
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
- package/dist/commands/tasks/actions/attach.js +32 -7
- package/dist/commands/tasks/complete.js +13 -0
- package/dist/commands/tasks/create.js +1 -1
- package/dist/commands/tasks/events/append.js +29 -6
- package/dist/commands/tasks/expectations/assess.d.ts +1 -0
- package/dist/commands/tasks/expectations/assess.js +43 -5
- package/dist/commands/tasks/expectations.js +77 -16
- package/dist/commands/tasks/follow-up.js +2 -2
- package/dist/commands/tasks/lifecycle/definitions.js +21 -2
- package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
- package/dist/commands/tasks/lifecycle/detach.js +43 -3
- package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/migrate.js +42 -3
- package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/reopen.js +40 -3
- package/dist/commands/tasks/lifecycle/transition.js +33 -2
- package/dist/commands/tasks/list.js +16 -9
- package/dist/commands/tasks/move.js +11 -13
- package/dist/commands/tasks/review-queue.d.ts +15 -0
- package/dist/commands/tasks/review-queue.js +180 -31
- package/dist/commands/tasks/show.d.ts +11 -0
- package/dist/commands/tasks/show.js +105 -12
- package/dist/commands/tasks/trust.js +7 -2
- package/dist/commands/tasks/update.js +46 -10
- package/dist/commands/tasks/updates/add.js +1 -1
- package/dist/commands/token/list.d.ts +22 -0
- package/dist/commands/token/list.js +68 -0
- package/dist/commands/token/revoke.d.ts +23 -0
- package/dist/commands/token/revoke.js +53 -0
- package/dist/lib/agent-actions.d.ts +57 -2
- package/dist/lib/agent-actions.js +113 -21
- package/dist/lib/agent-pin-mute.d.ts +10 -5
- package/dist/lib/agent-pin-mute.js +17 -4
- package/dist/lib/agent-visual-refs.d.ts +27 -0
- package/dist/lib/agent-visual-refs.js +127 -2
- package/dist/lib/agentic-stream.d.ts +6 -0
- package/dist/lib/agentic-stream.js +34 -3
- package/dist/lib/assignee-resolver.d.ts +1 -1
- package/dist/lib/assignee-resolver.js +8 -0
- package/dist/lib/bulk-task-targets.d.ts +12 -0
- package/dist/lib/bulk-task-targets.js +34 -1
- package/dist/lib/cli-installers.d.ts +130 -0
- package/dist/lib/cli-installers.js +145 -0
- package/dist/lib/command-miss.js +55 -7
- package/dist/lib/dedicated-lease-command.d.ts +13 -0
- package/dist/lib/dedicated-lease-command.js +22 -1
- package/dist/lib/dedicated-machines.d.ts +95 -1
- package/dist/lib/dedicated-machines.js +236 -2
- package/dist/lib/failed-lookup.d.ts +3 -0
- package/dist/lib/failed-lookup.js +40 -0
- package/dist/lib/first-party-harness-agent.js +9 -2
- package/dist/lib/first-party-harness-doctor.d.ts +15 -0
- package/dist/lib/first-party-harness-doctor.js +126 -27
- package/dist/lib/followups.d.ts +14 -6
- package/dist/lib/followups.js +31 -6
- package/dist/lib/harnesses.d.ts +8 -0
- package/dist/lib/instruction-input.d.ts +19 -1
- package/dist/lib/instruction-input.js +52 -1
- package/dist/lib/label-scope.d.ts +15 -0
- package/dist/lib/label-scope.js +25 -5
- package/dist/lib/local-skills.d.ts +98 -0
- package/dist/lib/local-skills.js +195 -0
- package/dist/lib/machines.d.ts +10 -10
- package/dist/lib/machines.js +23 -1
- package/dist/lib/search-query.d.ts +19 -0
- package/dist/lib/search-query.js +35 -0
- package/dist/lib/space-custom-actions.d.ts +72 -0
- package/dist/lib/space-custom-actions.js +117 -0
- package/dist/lib/space-resolver.d.ts +1 -21
- package/dist/lib/space-resolver.js +19 -10
- package/dist/lib/task-closure.d.ts +3 -2
- package/dist/lib/task-closure.js +18 -20
- package/dist/lib/task-lifecycle-output.js +117 -9
- package/dist/lib/task-ref-resolver.d.ts +1 -17
- package/dist/lib/task-ref-resolver.js +18 -8
- package/dist/lib/tasks.js +9 -3
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
- package/oclif.manifest.json +26635 -25649
- package/package.json +9 -3
|
@@ -5,15 +5,51 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
5
5
|
const base_command_1 = require("../../../base-command");
|
|
6
6
|
class TasksLifecycleMigrate extends base_command_1.BaseCommand {
|
|
7
7
|
static description = 'Migrate a bound lifecycle to a newer published version, fencing the old generation';
|
|
8
|
-
static
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> tasks lifecycle definitions OSK-1234 # where a --version comes from',
|
|
10
|
+
'<%= config.bin %> tasks lifecycle migrate OSK-1234 --version wfv_… --dry-run',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ required: true, ignoreStdin: true, description: 'Task ID or identifier' }),
|
|
14
|
+
};
|
|
9
15
|
static flags = {
|
|
10
|
-
version: core_1.Flags.string({
|
|
16
|
+
version: core_1.Flags.string({
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'Target published version id — `tasks lifecycle definitions <task-id>` prints it',
|
|
19
|
+
}),
|
|
20
|
+
'dry-run': core_1.Flags.boolean({
|
|
21
|
+
description: 'Describe what migrating would do and exit without writing',
|
|
22
|
+
}),
|
|
11
23
|
json: core_1.Flags.boolean({ description: 'Output JSON' }),
|
|
12
24
|
};
|
|
13
25
|
async run() {
|
|
14
26
|
this.requireAuth();
|
|
15
27
|
const { args, flags } = await this.parse(TasksLifecycleMigrate);
|
|
16
28
|
const taskId = await this.resolveTaskReference(args.id);
|
|
29
|
+
// Moving a Task between published protocols is not obviously reversible,
|
|
30
|
+
// and the old output said only that a run had started. A flag rather than a
|
|
31
|
+
// prompt, so the command stays usable from a script.
|
|
32
|
+
if (flags['dry-run']) {
|
|
33
|
+
let current;
|
|
34
|
+
try {
|
|
35
|
+
current = await data_provider_1.dataService.getTaskLifecycle(taskId);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
this.handleApiError(error);
|
|
39
|
+
}
|
|
40
|
+
if (flags.json)
|
|
41
|
+
return this.log(JSON.stringify({ dryRun: true, ...current }, null, 2));
|
|
42
|
+
const lifecycle = current.lifecycle;
|
|
43
|
+
if (!lifecycle)
|
|
44
|
+
return this.log('No lifecycle is attached to this Task — nothing to migrate.');
|
|
45
|
+
this.log(`Would migrate from version ${lifecycle.versionId} to ${flags.version}, as generation ` +
|
|
46
|
+
`${Number(lifecycle.generation) + 1}.`);
|
|
47
|
+
this.log(` It re-enters at the CURRENT stage (${lifecycle.currentStageKey ?? '—'}) when that stage` +
|
|
48
|
+
" still exists on the target version; otherwise the protocol's entry stage decides.");
|
|
49
|
+
this.log(' The old generation is fenced — its run can no longer write.');
|
|
50
|
+
this.log(' Earlier generations stay readable in `tasks lifecycle history`.');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
17
53
|
let result;
|
|
18
54
|
try {
|
|
19
55
|
result = await data_provider_1.dataService.migrateTaskLifecycle({ taskId, versionId: flags.version });
|
|
@@ -23,7 +59,10 @@ class TasksLifecycleMigrate extends base_command_1.BaseCommand {
|
|
|
23
59
|
}
|
|
24
60
|
if (flags.json)
|
|
25
61
|
return this.log(JSON.stringify(result, null, 2));
|
|
26
|
-
|
|
62
|
+
const lifecycle = result.lifecycle;
|
|
63
|
+
this.log(`Migrated to version ${lifecycle?.versionId ?? flags.version} — now generation ` +
|
|
64
|
+
`${lifecycle?.generation ?? '?'} at stage ${lifecycle?.currentStageKey ?? '—'}` +
|
|
65
|
+
` (run ${result.run.id}, ${result.run.status}).`);
|
|
27
66
|
}
|
|
28
67
|
}
|
|
29
68
|
exports.default = TasksLifecycleMigrate;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { BaseCommand } from '../../../base-command';
|
|
2
2
|
export default class TasksLifecycleReopen extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
|
+
static examples: string[];
|
|
4
5
|
static args: {
|
|
5
6
|
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
7
|
};
|
|
7
8
|
static flags: {
|
|
8
9
|
stage: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
10
|
reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
12
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
13
|
};
|
|
12
14
|
run(): Promise<void>;
|
|
@@ -5,16 +5,50 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
5
5
|
const base_command_1 = require("../../../base-command");
|
|
6
6
|
class TasksLifecycleReopen extends base_command_1.BaseCommand {
|
|
7
7
|
static description = 'Fence the current lifecycle generation and reopen at an earlier stage';
|
|
8
|
-
static
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> tasks lifecycle reopen OSK-1234 --dry-run',
|
|
10
|
+
'<%= config.bin %> tasks lifecycle reopen OSK-1234 --stage fix --reason "The fix did not take"',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ required: true, ignoreStdin: true, description: 'Task ID or identifier' }),
|
|
14
|
+
};
|
|
9
15
|
static flags = {
|
|
10
16
|
stage: core_1.Flags.string({ description: 'Stage key to re-enter at; defaults to the entry stage' }),
|
|
11
|
-
reason: core_1.Flags.string({ description: 'Why the lifecycle is reopening' }),
|
|
17
|
+
reason: core_1.Flags.string({ description: 'Why the lifecycle is reopening; recorded in history' }),
|
|
18
|
+
'dry-run': core_1.Flags.boolean({
|
|
19
|
+
description: 'Describe what reopening would do and exit without writing',
|
|
20
|
+
}),
|
|
12
21
|
json: core_1.Flags.boolean({ description: 'Output JSON' }),
|
|
13
22
|
};
|
|
14
23
|
async run() {
|
|
15
24
|
this.requireAuth();
|
|
16
25
|
const { args, flags } = await this.parse(TasksLifecycleReopen);
|
|
17
26
|
const taskId = await this.resolveTaskReference(args.id);
|
|
27
|
+
// Reopen advances a generation and is not obviously reversible. A preview
|
|
28
|
+
// that names the target stage is cheaper than learning what it did by
|
|
29
|
+
// diffing state afterwards, and a flag keeps the command scriptable where
|
|
30
|
+
// an interactive prompt would not.
|
|
31
|
+
if (flags['dry-run']) {
|
|
32
|
+
let current;
|
|
33
|
+
try {
|
|
34
|
+
current = await data_provider_1.dataService.getTaskLifecycle(taskId);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.handleApiError(error);
|
|
38
|
+
}
|
|
39
|
+
if (flags.json)
|
|
40
|
+
return this.log(JSON.stringify({ dryRun: true, ...current }, null, 2));
|
|
41
|
+
const lifecycle = current.lifecycle;
|
|
42
|
+
if (!lifecycle)
|
|
43
|
+
return this.log('No lifecycle is attached to this Task — nothing to reopen.');
|
|
44
|
+
const entry = flags.stage || current.stages?.[0]?.key;
|
|
45
|
+
this.log(`Would reopen at stage ${entry ?? '(entry stage)'} as generation ${Number(lifecycle.generation) + 1}` +
|
|
46
|
+
` (currently generation ${lifecycle.generation}, stage ${lifecycle.currentStageKey ?? '—'}).`);
|
|
47
|
+
this.log(' The current run is cancelled; its transitions stay readable in history.');
|
|
48
|
+
this.log(" A terminal Task is brought back to the entry stage's declared status.");
|
|
49
|
+
this.log(' Stages the earlier generation finished keep reading as done in that generation.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
18
52
|
let result;
|
|
19
53
|
try {
|
|
20
54
|
result = await data_provider_1.dataService.reopenTaskLifecycle({
|
|
@@ -28,7 +62,10 @@ class TasksLifecycleReopen extends base_command_1.BaseCommand {
|
|
|
28
62
|
}
|
|
29
63
|
if (flags.json)
|
|
30
64
|
return this.log(JSON.stringify(result, null, 2));
|
|
31
|
-
|
|
65
|
+
// Name where it landed, not just that it happened.
|
|
66
|
+
const lifecycle = result.lifecycle;
|
|
67
|
+
this.log(`Reopened at stage ${lifecycle?.currentStageKey ?? '—'} as generation ${lifecycle?.generation ?? '?'}` +
|
|
68
|
+
` — run ${result.run.id} (${result.run.status}). The previous generation stays in history.`);
|
|
32
69
|
}
|
|
33
70
|
}
|
|
34
71
|
exports.default = TasksLifecycleReopen;
|
|
@@ -42,6 +42,23 @@ class TasksLifecycleTransition extends base_command_1.BaseCommand {
|
|
|
42
42
|
this.error('--payload must be a JSON object.', { exit: 2 });
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
/*
|
|
46
|
+
* The fence only protects the interval it is given.
|
|
47
|
+
*
|
|
48
|
+
* `--expected-revision` is the caller's claim about the state they DECIDED
|
|
49
|
+
* against. When it is omitted this command re-reads the lifecycle and sends
|
|
50
|
+
* back what it just read — and a compare-and-set whose expected value was
|
|
51
|
+
* fetched microseconds before the write can only catch that microsecond,
|
|
52
|
+
* never the interval that matters, which is between the human's read and
|
|
53
|
+
* their write. So the default is a convenience, not protection, and it was
|
|
54
|
+
* silent about that: a `confirm` decided at revision 0 applied against
|
|
55
|
+
* revision 1 and closed a Task, with nothing in the output saying the world
|
|
56
|
+
* had moved in between.
|
|
57
|
+
*
|
|
58
|
+
* Kept as the default because requiring a number on every call would be
|
|
59
|
+
* paid by everyone to protect a minority of flows — but it now says what it
|
|
60
|
+
* fenced on and how to fence properly.
|
|
61
|
+
*/
|
|
45
62
|
let expectedRevision = flags['expected-revision'];
|
|
46
63
|
if (expectedRevision === undefined) {
|
|
47
64
|
let current;
|
|
@@ -55,6 +72,10 @@ class TasksLifecycleTransition extends base_command_1.BaseCommand {
|
|
|
55
72
|
this.error('No lifecycle is attached to this Task.', { exit: 2 });
|
|
56
73
|
}
|
|
57
74
|
expectedRevision = current.lifecycle.revision;
|
|
75
|
+
if (!flags.json) {
|
|
76
|
+
this.log(`Fencing on revision ${expectedRevision}, read just now — not the revision you saw. ` +
|
|
77
|
+
'Pass --expected-revision <n> to fence on the state you decided against.');
|
|
78
|
+
}
|
|
58
79
|
}
|
|
59
80
|
const idempotencyKey = flags['idempotency-key'] ?? `cli-lifecycle:${taskId}:${args.transition}:r${expectedRevision}`;
|
|
60
81
|
let response;
|
|
@@ -71,8 +92,18 @@ class TasksLifecycleTransition extends base_command_1.BaseCommand {
|
|
|
71
92
|
this.handleApiError(error);
|
|
72
93
|
}
|
|
73
94
|
if (flags.json)
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
this.log(JSON.stringify(response, null, 2));
|
|
96
|
+
else
|
|
97
|
+
(0, task_lifecycle_output_1.renderLifecycleTransitionResult)(response, (line) => this.log(line));
|
|
98
|
+
// Exit non-zero when the write did not land. `applied` is the only verdict
|
|
99
|
+
// that means the stage moved; `rejected`, `superseded` and `gated` all mean
|
|
100
|
+
// it did not. Exiting 0 on all four made
|
|
101
|
+
// `transition A && transition B` run B after A had been fenced off — which
|
|
102
|
+
// is precisely the lost update `expectedRevision` exists to prevent, waved
|
|
103
|
+
// through by the shell.
|
|
104
|
+
if (response.status !== 'applied') {
|
|
105
|
+
this.exit(1);
|
|
106
|
+
}
|
|
76
107
|
}
|
|
77
108
|
}
|
|
78
109
|
exports.default = TasksLifecycleTransition;
|
|
@@ -146,13 +146,13 @@ function buildListParams(flags, assigneeUserId) {
|
|
|
146
146
|
...(flag('actionability-reason')
|
|
147
147
|
? { actionabilityReason: flag('actionability-reason') }
|
|
148
148
|
: {}),
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
// This table always renders a NEXT column, so it always has to ask for the
|
|
150
|
+
// data behind it. Requesting it only alongside an actionability FILTER left
|
|
151
|
+
// the default board printing a header with nothing under it — and a plain
|
|
152
|
+
// `tasks list` is exactly where a person looks to find out what anything is
|
|
153
|
+
// waiting for. The server computes it in one batched read, so the cost is
|
|
154
|
+
// a column that works rather than a column that is there.
|
|
155
|
+
includeActionability: true,
|
|
156
156
|
...(flag('priority') ? { priority: flag('priority') } : {}),
|
|
157
157
|
...(flag('estimate') !== undefined ? { estimate: flag('estimate') } : {}),
|
|
158
158
|
...(flag('min-estimate') !== undefined
|
|
@@ -427,8 +427,13 @@ class TasksList extends base_command_1.BaseCommand {
|
|
|
427
427
|
priority: String(t.priority ?? '-'),
|
|
428
428
|
estimate: t.estimate === null || t.estimate === undefined ? '-' : String(t.estimate),
|
|
429
429
|
assignees,
|
|
430
|
+
// `reason` is the sentence the server wrote for a person; `reasonCode`
|
|
431
|
+
// is the enum it wrote for a machine. Printing the code rendered every
|
|
432
|
+
// stage of every lifecycle as `human:lifecycle_stage` — six different
|
|
433
|
+
// waits, one string — while the sentence naming the stage sat unread in
|
|
434
|
+
// the same payload.
|
|
430
435
|
next: actionability
|
|
431
|
-
? `${String(actionability.nextActor ?? '-')}
|
|
436
|
+
? `${String(actionability.nextActor ?? '-')}: ${String(actionability.reason || actionability.reasonCode || actionability.state || '-')}`
|
|
432
437
|
: String(t.executionMode ?? '-'),
|
|
433
438
|
match: String(t._matchField ?? '-'),
|
|
434
439
|
};
|
|
@@ -440,7 +445,9 @@ class TasksList extends base_command_1.BaseCommand {
|
|
|
440
445
|
{ key: 'priority', header: 'PRIORITY' },
|
|
441
446
|
{ key: 'estimate', header: 'ESTIMATE' },
|
|
442
447
|
{ key: 'assignees', header: 'ASSIGNEES' },
|
|
443
|
-
|
|
448
|
+
// Bounded: the reason is a sentence, and an unbounded column wraps the
|
|
449
|
+
// table into unreadability on a narrow terminal.
|
|
450
|
+
{ key: 'next', header: 'NEXT', maxWidth: 46 },
|
|
444
451
|
// Only meaningful on a search — the server computes it from the same
|
|
445
452
|
// relevance rank that orders the rows, and a plain listing has none.
|
|
446
453
|
...(flags.q ? [{ key: 'match', header: 'MATCH' }] : []),
|
|
@@ -5,6 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
5
5
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
6
6
|
const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
|
|
7
7
|
const base_command_1 = require("../../base-command");
|
|
8
|
+
const task_closure_1 = require("../../lib/task-closure");
|
|
8
9
|
const web_url_1 = require("../../lib/web-url");
|
|
9
10
|
const task_resolver_1 = require("../../lib/task-resolver");
|
|
10
11
|
const projects_1 = require("../../lib/projects");
|
|
@@ -155,19 +156,16 @@ class TasksMove extends base_command_1.BaseCommand {
|
|
|
155
156
|
// the loosely-typed receipt, so the narrowing cast stays; what matters is
|
|
156
157
|
// that the shape it names is the one data-provider owns.
|
|
157
158
|
const resolutionClosure = t.resolution?.closure;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (parts.length) {
|
|
169
|
-
this.log(` Closed over ${parts.join('; ')} (resolution.closure).`);
|
|
170
|
-
}
|
|
159
|
+
/*
|
|
160
|
+
* Through the shared renderer. This hand-rolled the rendering and pushed
|
|
161
|
+
* blocker CODES at a human — `Closed over TASK_RESULT_REQUIRED`, which is a
|
|
162
|
+
* wire identifier and, read plainly, the opposite of what just happened. The
|
|
163
|
+
* worst instance was the one this batch cares most about: a Task closed over
|
|
164
|
+
* a reviewer's objection printed `TASK_RESULT_CHANGES_REQUESTED`, rendering
|
|
165
|
+
* "somebody looked and objected" as an identifier.
|
|
166
|
+
*/
|
|
167
|
+
for (const sentence of (0, task_closure_1.closureSnapshotLines)(resolutionClosure)) {
|
|
168
|
+
this.log(` ${sentence}`);
|
|
171
169
|
}
|
|
172
170
|
}
|
|
173
171
|
/**
|
|
@@ -23,7 +23,22 @@ export default class TasksReviewQueue extends BaseCommand {
|
|
|
23
23
|
space: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
24
|
actionable: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
25
25
|
limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
|
+
'include-closed': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
26
27
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
28
|
};
|
|
28
29
|
run(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* An empty queue must not be how you learn a Space id is wrong.
|
|
32
|
+
*
|
|
33
|
+
* A UUID-shaped ref is accepted without a lookup (`base-command` short-circuits
|
|
34
|
+
* on the SHAPE, which is a cheap and correct optimisation nearly everywhere),
|
|
35
|
+
* and the list route deliberately never 404s — an unknown Space falls back to
|
|
36
|
+
* the ACL enumeration and returns an honest empty page. Those two reasonable
|
|
37
|
+
* decisions compose into "Nothing is awaiting a judgement", exit 0, for a
|
|
38
|
+
* Space that does not exist. A typo read as done.
|
|
39
|
+
*
|
|
40
|
+
* So the existence check is paid for ONLY on the path that is about to make
|
|
41
|
+
* the completeness claim — never on the path that found work.
|
|
42
|
+
*/
|
|
43
|
+
private assertSpaceExists;
|
|
29
44
|
}
|
|
@@ -35,6 +35,9 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
|
|
|
35
35
|
description: 'Only Tasks where a Result is already in — the ones a reviewer can act on',
|
|
36
36
|
}),
|
|
37
37
|
limit: core_1.Flags.integer({ description: 'Maximum Tasks to inspect (default 100)', default: 100 }),
|
|
38
|
+
'include-closed': core_1.Flags.boolean({
|
|
39
|
+
description: 'Also list Tasks already closed — for auditing what was closed over',
|
|
40
|
+
}),
|
|
38
41
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
39
42
|
};
|
|
40
43
|
async run() {
|
|
@@ -66,7 +69,27 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
|
|
|
66
69
|
// unlooked-for empty answer this command refuses to give.
|
|
67
70
|
this.error('--limit must be at least 1.', { exit: 1 });
|
|
68
71
|
}
|
|
72
|
+
/*
|
|
73
|
+
* oclif's `required: true` asserts the flag was SUPPLIED, not that it says
|
|
74
|
+
* anything. `--space ""` — an unset shell variable, which is how it happens
|
|
75
|
+
* — satisfied it, and the empty string was then dropped from the query
|
|
76
|
+
* string as falsy, so the command silently widened from one Space to every
|
|
77
|
+
* Space the caller can see and reported that as the answer.
|
|
78
|
+
*/
|
|
79
|
+
if (!flags.space.trim()) {
|
|
80
|
+
this.error('--space needs a Space id; it was given an empty value.', { exit: 1 });
|
|
81
|
+
}
|
|
69
82
|
const tasks = [];
|
|
83
|
+
/*
|
|
84
|
+
* Whether the scan actually reached the end.
|
|
85
|
+
*
|
|
86
|
+
* The loop already followed the cursor, and then stopped at `--limit` with
|
|
87
|
+
* `has_more` still true and threw that fact away — so a Space with 95 owed
|
|
88
|
+
* Expectations reported on the first 11 and printed the same sentence it
|
|
89
|
+
* prints for a Space that owes nothing. The comment above states the
|
|
90
|
+
* principle this had to satisfy; nothing recorded whether it had.
|
|
91
|
+
*/
|
|
92
|
+
let truncated = false;
|
|
70
93
|
try {
|
|
71
94
|
let cursor;
|
|
72
95
|
for (;;) {
|
|
@@ -80,8 +103,12 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
|
|
|
80
103
|
'say whether anything is owed. Refusing to report an empty queue.', { exit: 1 });
|
|
81
104
|
}
|
|
82
105
|
tasks.push(...response.data);
|
|
83
|
-
if (!response.has_more || !response.next_cursor
|
|
106
|
+
if (!response.has_more || !response.next_cursor)
|
|
107
|
+
break;
|
|
108
|
+
if (tasks.length >= flags.limit) {
|
|
109
|
+
truncated = true;
|
|
84
110
|
break;
|
|
111
|
+
}
|
|
85
112
|
cursor = response.next_cursor;
|
|
86
113
|
}
|
|
87
114
|
}
|
|
@@ -95,34 +122,101 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
|
|
|
95
122
|
* worse than no queue: the reviewer would believe they had seen everything.
|
|
96
123
|
*/
|
|
97
124
|
const rows = [];
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
/*
|
|
126
|
+
* A finished Task is not a worklist item.
|
|
127
|
+
*
|
|
128
|
+
* Closing is the closer's call now, so a Task can reach `done` over
|
|
129
|
+
* Expectations nobody judged — and those rows stayed in the queue forever,
|
|
130
|
+
* captioned in the present tense ("settling these will not close it") about
|
|
131
|
+
* something already closed. Every forced close permanently added noise to
|
|
132
|
+
* the list a reviewer is supposed to work down. `--include-closed` keeps the
|
|
133
|
+
* old view for anyone auditing what was closed over.
|
|
134
|
+
*/
|
|
135
|
+
const CLOSED_STATUS_TYPES = new Set(['completed', 'cancelled']);
|
|
136
|
+
const candidates = tasks.filter((task) => flags['include-closed'] || !CLOSED_STATUS_TYPES.has(String(task.statusType ?? '')));
|
|
137
|
+
const skippedClosed = tasks.length - candidates.length;
|
|
138
|
+
/*
|
|
139
|
+
* Acceptance is one request per Task and there is no batch endpoint, so the
|
|
140
|
+
* serial loop this replaces took 61 seconds on 100 Tasks and printed nothing
|
|
141
|
+
* at all until it finished. Bounded rather than `Promise.all`: 100
|
|
142
|
+
* simultaneous authorized reads hit the same cascade the `/cards` endpoint
|
|
143
|
+
* exists to avoid. Results are written by INDEX so output order stays the
|
|
144
|
+
* list's order regardless of which request returns first.
|
|
145
|
+
*/
|
|
146
|
+
const ACCEPTANCE_CONCURRENCY = 6;
|
|
147
|
+
const settled = new Array(candidates.length).fill(null);
|
|
148
|
+
let next = 0;
|
|
149
|
+
const worker = async () => {
|
|
150
|
+
for (;;) {
|
|
151
|
+
const index = next;
|
|
152
|
+
next += 1;
|
|
153
|
+
if (index >= candidates.length)
|
|
154
|
+
return;
|
|
155
|
+
const task = candidates[index];
|
|
156
|
+
const taskId = String(task.id ?? task._id ?? '');
|
|
157
|
+
if (!taskId)
|
|
158
|
+
continue;
|
|
159
|
+
const base = {
|
|
160
|
+
taskId,
|
|
161
|
+
identifier: String(task.identifier ?? taskId),
|
|
162
|
+
title: String(task.title ?? '(untitled)'),
|
|
163
|
+
status: String(task.status ?? '-'),
|
|
164
|
+
hasResult: Boolean(task.currentResultId),
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
const acceptance = await data_provider_1.dataService.getTaskAcceptance(taskId);
|
|
168
|
+
const awaiting = acceptance.expectations.filter((e) => e.gating && !e.met);
|
|
169
|
+
if (awaiting.length > 0)
|
|
170
|
+
settled[index] = { ...base, awaiting };
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
settled[index] = {
|
|
174
|
+
...base,
|
|
175
|
+
awaiting: [],
|
|
176
|
+
error: error?.message || 'unreadable',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
117
179
|
}
|
|
118
|
-
}
|
|
180
|
+
};
|
|
181
|
+
await Promise.all(Array.from({ length: Math.min(ACCEPTANCE_CONCURRENCY, candidates.length) }, worker));
|
|
182
|
+
rows.push(...settled.filter((row) => row !== null));
|
|
119
183
|
const visible = flags.actionable ? rows.filter((row) => row.hasResult || row.error) : rows;
|
|
120
184
|
if (flags.json) {
|
|
121
|
-
this.log(JSON.stringify({
|
|
185
|
+
this.log(JSON.stringify({
|
|
186
|
+
space: flags.space,
|
|
187
|
+
inspected: candidates.length,
|
|
188
|
+
truncated,
|
|
189
|
+
...(skippedClosed > 0 ? { skippedClosed } : {}),
|
|
190
|
+
owedTasks: rows.length,
|
|
191
|
+
tasks: visible,
|
|
192
|
+
}, null, 2));
|
|
122
193
|
return;
|
|
123
194
|
}
|
|
124
|
-
|
|
195
|
+
/*
|
|
196
|
+
* "Nothing is owed" may only be said by a scan that reached the end.
|
|
197
|
+
*
|
|
198
|
+
* One sentence used to cover four different states — a clean queue, a
|
|
199
|
+
* truncated scan, a filter that emptied the list, and a Space that does not
|
|
200
|
+
* exist — and in three of them it was false. Each now says its own thing,
|
|
201
|
+
* and the two that cannot support the claim refuse to make it, in the same
|
|
202
|
+
* shape as the misread-envelope guard above.
|
|
203
|
+
*/
|
|
204
|
+
if (truncated && rows.length === 0) {
|
|
205
|
+
this.error(`Inspected only the first ${candidates.length} Task(s) of a longer list in ${flags.space}, ` +
|
|
206
|
+
'so it cannot say whether anything is owed. Refusing to report an empty queue. ' +
|
|
207
|
+
'Re-run with a larger --limit.', { exit: 1 });
|
|
208
|
+
}
|
|
209
|
+
if (rows.length === 0) {
|
|
210
|
+
await this.assertSpaceExists(flags.space);
|
|
125
211
|
this.log(`Nothing in ${flags.space} is awaiting a judgement.`);
|
|
212
|
+
if (skippedClosed > 0) {
|
|
213
|
+
this.log(` (${skippedClosed} closed Task(s) skipped — see them with --include-closed.)`);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (visible.length === 0) {
|
|
218
|
+
this.log(`${rows.length} Task(s) in ${flags.space} owe judgements, but none has a Result yet — ` +
|
|
219
|
+
're-run without --actionable to see them.');
|
|
126
220
|
return;
|
|
127
221
|
}
|
|
128
222
|
let total = 0;
|
|
@@ -133,18 +227,37 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
|
|
|
133
227
|
this.log(` could not read its Expectations: ${row.error}`);
|
|
134
228
|
continue;
|
|
135
229
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
230
|
+
/*
|
|
231
|
+
* State the fact, not a gate. This read "no Result submitted yet — settling
|
|
232
|
+
* these will not close it", which implied that a Result is what stands
|
|
233
|
+
* between these judgements and a close. Since `56a6ded703` it is not: a
|
|
234
|
+
* Task closes without one and the absence is recorded on
|
|
235
|
+
* `resolution.closure`. What a reviewer needs to know is only that there is
|
|
236
|
+
* no Result to judge the work against yet.
|
|
237
|
+
*/
|
|
139
238
|
if (!row.hasResult) {
|
|
140
|
-
this.log(' no Result submitted yet —
|
|
239
|
+
this.log(' no Result submitted yet — nothing to judge the work against');
|
|
141
240
|
}
|
|
142
241
|
for (const expectation of row.awaiting) {
|
|
143
242
|
total += 1;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
243
|
+
this.log(` ${expectation.id} ${expectation.sentence || expectation.text || ''}`);
|
|
244
|
+
/*
|
|
245
|
+
* The judgement itself, not just the KIND of judge.
|
|
246
|
+
*
|
|
247
|
+
* `[last judged by person]` named no person, no decision, no date and no
|
|
248
|
+
* reason — so a reviewer working down this list could see that someone
|
|
249
|
+
* had looked and not what they concluded, which is the one thing they
|
|
250
|
+
* need before looking again. The acceptance projection carries all of it
|
|
251
|
+
* now; this is where it was being dropped.
|
|
252
|
+
*/
|
|
253
|
+
if (expectation.assessedByKind) {
|
|
254
|
+
const decision = expectation.decision || 'judged';
|
|
255
|
+
const when = expectation.assessedAt
|
|
256
|
+
? ` on ${String(expectation.assessedAt).slice(0, 10)}`
|
|
257
|
+
: '';
|
|
258
|
+
const why = expectation.reason ? `: ${expectation.reason}` : '';
|
|
259
|
+
this.log(` ${decision} by ${(0, task_closure_1.describeAssessor)(expectation.assessedByKind)}${when}${why}`);
|
|
260
|
+
}
|
|
148
261
|
if (expectation.evidenceLevel) {
|
|
149
262
|
this.log(` evidence: ${expectation.evidenceLevel}${expectation.requiredEvidence ? ` (asked for ${expectation.requiredEvidence})` : ''}`);
|
|
150
263
|
}
|
|
@@ -155,11 +268,47 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
|
|
|
155
268
|
}
|
|
156
269
|
this.log('');
|
|
157
270
|
this.log(`${total} Expectation(s) awaiting a judgement across ${visible.length} Task(s).`);
|
|
271
|
+
if (truncated) {
|
|
272
|
+
this.log(`Only the first ${candidates.length} Task(s) were inspected and more remain — ` +
|
|
273
|
+
're-run with a larger --limit to see the rest.');
|
|
274
|
+
}
|
|
275
|
+
if (skippedClosed > 0) {
|
|
276
|
+
this.log(`${skippedClosed} closed Task(s) skipped — see them with --include-closed.`);
|
|
277
|
+
}
|
|
158
278
|
this.log('');
|
|
159
279
|
this.log('Settle one: skrr tasks expectations assess <task-id> <expectation-id> \\');
|
|
160
280
|
this.log(' --decision met --reason "..."');
|
|
161
281
|
this.log('Settle a Task: skrr tasks expectations assess <task-id> --all \\');
|
|
162
282
|
this.log(' --decision met --reason "..."');
|
|
163
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* An empty queue must not be how you learn a Space id is wrong.
|
|
286
|
+
*
|
|
287
|
+
* A UUID-shaped ref is accepted without a lookup (`base-command` short-circuits
|
|
288
|
+
* on the SHAPE, which is a cheap and correct optimisation nearly everywhere),
|
|
289
|
+
* and the list route deliberately never 404s — an unknown Space falls back to
|
|
290
|
+
* the ACL enumeration and returns an honest empty page. Those two reasonable
|
|
291
|
+
* decisions compose into "Nothing is awaiting a judgement", exit 0, for a
|
|
292
|
+
* Space that does not exist. A typo read as done.
|
|
293
|
+
*
|
|
294
|
+
* So the existence check is paid for ONLY on the path that is about to make
|
|
295
|
+
* the completeness claim — never on the path that found work.
|
|
296
|
+
*/
|
|
297
|
+
async assertSpaceExists(spaceId) {
|
|
298
|
+
try {
|
|
299
|
+
await data_provider_1.dataService.getSpace(spaceId);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
// The status rides the error itself here, the way `base-command` reads it
|
|
303
|
+
// (`e.status === 404`) — not on a nested `response`.
|
|
304
|
+
const status = error?.status;
|
|
305
|
+
if (status === 404 || status === 403) {
|
|
306
|
+
this.error(`No Space ${spaceId} is visible to you, so there is nothing to report on. ` +
|
|
307
|
+
'Check the id with `skrr spaces list`.', { exit: 2 });
|
|
308
|
+
}
|
|
309
|
+
// Any other failure is about this read, not about the Space. Let the
|
|
310
|
+
// empty answer stand rather than turning a blip into a false "not found".
|
|
311
|
+
}
|
|
312
|
+
}
|
|
164
313
|
}
|
|
165
314
|
exports.default = TasksReviewQueue;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Where to look next — as COMMANDS, not as facts about the task.
|
|
4
|
+
*
|
|
5
|
+
* These used to print as `What the agent produced: skrr tasks output <ref>`,
|
|
6
|
+
* in the same `Label: value` grammar as every line above them, so the line read
|
|
7
|
+
* as a statement of what the agent had produced. It also printed on tasks no
|
|
8
|
+
* agent had ever run, asserting output that did not exist (OSK-9669). They are
|
|
9
|
+
* now a visibly separate list, and only once a run, a conversation or a Result
|
|
10
|
+
* exists for them to show.
|
|
11
|
+
*/
|
|
12
|
+
export declare function taskPointerLines(t: Record<string, unknown>, ref: string, bin?: string): string[];
|
|
2
13
|
/**
|
|
3
14
|
* The failure rows of `tasks show`, when there are any. (S3 dogfood finding)
|
|
4
15
|
*
|