@skrr-ai/cli 0.1.63 → 0.1.65

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.
Files changed (40) hide show
  1. package/dist/commands/goals/plan-now.d.ts +6 -0
  2. package/dist/commands/goals/plan-now.js +25 -0
  3. package/dist/commands/memory/approvals/issue.js +6 -3
  4. package/dist/commands/memory/stores/update.js +6 -3
  5. package/dist/commands/skills/visibility.d.ts +15 -0
  6. package/dist/commands/skills/visibility.js +49 -0
  7. package/dist/commands/store/admissions/approve.d.ts +1 -0
  8. package/dist/commands/store/admissions/approve.js +9 -3
  9. package/dist/commands/store/admissions/list.js +6 -0
  10. package/dist/commands/store/admissions/refuse.d.ts +1 -0
  11. package/dist/commands/store/admissions/refuse.js +9 -3
  12. package/dist/commands/store/artifacts/release.d.ts +3 -0
  13. package/dist/commands/store/artifacts/release.js +42 -0
  14. package/dist/commands/store/installs.d.ts +10 -0
  15. package/dist/commands/store/installs.js +54 -0
  16. package/dist/commands/store/updates.js +1 -1
  17. package/dist/commands/tasks/comments/list.d.ts +10 -0
  18. package/dist/commands/tasks/comments/list.js +53 -9
  19. package/dist/commands/tasks/comments/react.js +3 -1
  20. package/dist/commands/tasks/deliverable/add.d.ts +1 -0
  21. package/dist/commands/tasks/deliverable/add.js +13 -2
  22. package/dist/commands/tasks/deliverable/list.js +15 -1
  23. package/dist/commands/tasks/lifecycle/transition.js +6 -3
  24. package/dist/commands/tasks/plan/run-update.js +1 -1
  25. package/dist/commands/tasks/result/submit.js +1 -1
  26. package/dist/commands/tasks/resume/save.js +6 -3
  27. package/dist/commands/tasks/updates/add.js +11 -1
  28. package/dist/lib/file-mime.js +1 -0
  29. package/dist/lib/live-runtime-binary.d.ts +6 -0
  30. package/dist/lib/live-runtime-binary.js +32 -1
  31. package/dist/lib/node-adapter.js +10 -5
  32. package/dist/lib/prompt.d.ts +7 -0
  33. package/dist/lib/prompt.js +16 -0
  34. package/dist/lib/task-asset-upload.d.ts +5 -0
  35. package/dist/lib/task-asset-upload.js +4 -0
  36. package/dist/lib/task-closure.d.ts +3 -1
  37. package/dist/lib/task-closure.js +14 -4
  38. package/dist/node_modules/@skrr-ai/data-provider/index.js +10 -2
  39. package/oclif.manifest.json +24195 -24018
  40. package/package.json +1 -1
@@ -23,6 +23,12 @@ export declare function describePlannerReason(reason: string | null | undefined)
23
23
  * saying so.
24
24
  */
25
25
  export declare function plannerTickDidNotRun(result: GoalPlanNowResponse | undefined): boolean;
26
+ /**
27
+ * HTTP 202: the tick was handed to the planner worker rather than awaited in
28
+ * the request. `processed` is absent by design — it is neither a success to
29
+ * count nor a skip to fail on, it is a hand-off (OSK-12748).
30
+ */
31
+ export declare function plannerTickAccepted(result: GoalPlanNowResponse | undefined): boolean;
26
32
  /** Did the REQUEST give up, as opposed to the tick failing? */
27
33
  export declare function isPlannerRequestTimeout(err: unknown): boolean;
28
34
  /**
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.describePlannerReason = describePlannerReason;
4
4
  exports.plannerTickDidNotRun = plannerTickDidNotRun;
5
+ exports.plannerTickAccepted = plannerTickAccepted;
5
6
  exports.isPlannerRequestTimeout = isPlannerRequestTimeout;
6
7
  exports.plannerTimeoutMessage = plannerTimeoutMessage;
7
8
  const core_1 = require("@oclif/core");
@@ -85,6 +86,14 @@ function describePlannerReason(reason) {
85
86
  function plannerTickDidNotRun(result) {
86
87
  return result?.ok === false || result?.processed === false;
87
88
  }
89
+ /**
90
+ * HTTP 202: the tick was handed to the planner worker rather than awaited in
91
+ * the request. `processed` is absent by design — it is neither a success to
92
+ * count nor a skip to fail on, it is a hand-off (OSK-12748).
93
+ */
94
+ function plannerTickAccepted(result) {
95
+ return result?.accepted === true;
96
+ }
88
97
  /** Did the REQUEST give up, as opposed to the tick failing? */
89
98
  function isPlannerRequestTimeout(err) {
90
99
  const status = err?.status;
@@ -163,6 +172,22 @@ class GoalsPlanNow extends base_command_1.BaseCommand {
163
172
  }
164
173
  const r = result;
165
174
  const reasonText = describePlannerReason(r?.reason);
175
+ if (plannerTickAccepted(r)) {
176
+ // HTTP 202: the tick was handed to the planner worker — no counts exist
177
+ // yet, and that is the honest thing to print (OSK-12748). A 502 here used
178
+ // to mean "gateway gave up mid-run"; accepted means the request ended
179
+ // BEFORE the run, so nothing was left ambiguously in flight.
180
+ if (flags.json) {
181
+ this.log(JSON.stringify(r, null, 2));
182
+ return;
183
+ }
184
+ this.log(`Planner tick accepted for goal ${args.id} — running in the background.`);
185
+ if (r.backfilledCount) {
186
+ this.log(` backfilled: ${r.backfilledCount} existing task(s) tagged to the space.`);
187
+ }
188
+ this.log(` Watch \`${this.config.bin} goals tasks ${args.id}\` for what it creates.`);
189
+ return;
190
+ }
166
191
  if (plannerTickDidNotRun(r)) {
167
192
  // The backfill runs BEFORE planning and unconditionally, so a skipped
168
193
  // tick can still have moved rows. Saying "nothing happened" would be the
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const scoped_memory_command_1 = require("../../../lib/scoped-memory-command");
5
+ const prompt_1 = require("../../../lib/prompt");
5
6
  class MemoryApprovalsIssue extends scoped_memory_command_1.ScopedMemoryCommand {
6
7
  static description = 'Issue a short-lived user approval for Space PII or Agent-to-Space promotion';
7
8
  static flags = {
@@ -35,10 +36,12 @@ class MemoryApprovalsIssue extends scoped_memory_command_1.ScopedMemoryCommand {
35
36
  this.error('--payload JSON is required for --purpose space_pii.', { exit: 2 });
36
37
  }
37
38
  try {
38
- payload = JSON.parse(flags.payload);
39
+ payload = (0, prompt_1.parseJsonFlag)(flags.payload, '--payload');
39
40
  }
40
- catch {
41
- this.error('--payload must be valid JSON.', { exit: 2 });
41
+ catch (err) {
42
+ this.error(err instanceof Error ? err.message : '--payload must be valid JSON.', {
43
+ exit: 2,
44
+ });
42
45
  }
43
46
  }
44
47
  else if (!flags['source-store'] || !flags['source-memory'] || !flags['source-version']) {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const scoped_memory_command_1 = require("../../../lib/scoped-memory-command");
5
+ const prompt_1 = require("../../../lib/prompt");
5
6
  class MemoryStoresUpdate extends scoped_memory_command_1.ScopedMemoryCommand {
6
7
  static description = 'Update Memory Store metadata with revision CAS';
7
8
  static flags = {
@@ -18,10 +19,12 @@ class MemoryStoresUpdate extends scoped_memory_command_1.ScopedMemoryCommand {
18
19
  const store = await this.resolveStore(flags, { write: true });
19
20
  let attributes;
20
21
  try {
21
- attributes = JSON.parse(flags.attributes);
22
+ attributes = (0, prompt_1.parseJsonFlag)(flags.attributes, '--attributes');
22
23
  }
23
- catch {
24
- this.error('--attributes must be valid JSON.', { exit: 2 });
24
+ catch (err) {
25
+ this.error(err instanceof Error ? err.message : '--attributes must be valid JSON.', {
26
+ exit: 2,
27
+ });
25
28
  }
26
29
  const body = {
27
30
  attributes,
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class SkillsVisibility extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ public: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ private: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ price: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ class SkillsVisibility extends base_command_1.BaseCommand {
7
+ static description = 'Publish or unpublish an owned skill with a governed Store release';
8
+ static examples = [
9
+ '<%= config.bin %> store artifacts release skill <skill-id> --versionLabel 1.0 --artifactRef skill:<skill-id> --permission-manifest <json>',
10
+ '<%= config.bin %> skills visibility <skill-id> --public',
11
+ '<%= config.bin %> skills visibility <skill-id> --private',
12
+ ];
13
+ static args = {
14
+ id: core_1.Args.string({ description: 'Skill ID', required: true, ignoreStdin: true }),
15
+ };
16
+ static flags = {
17
+ public: core_1.Flags.boolean({ description: 'Publish the skill', exclusive: ['private'] }),
18
+ private: core_1.Flags.boolean({ description: 'Unpublish the skill', exclusive: ['public'] }),
19
+ price: core_1.Flags.integer({ description: 'Listing price in micro-USD' }),
20
+ json: core_1.Flags.boolean({ description: 'Output the result as JSON' }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(SkillsVisibility);
25
+ if (!flags.public && !flags.private) {
26
+ this.error('Pass either --public or --private.', { exit: 2 });
27
+ }
28
+ if (flags.private && flags.price !== undefined) {
29
+ this.error('--price applies only when publishing with --public.', { exit: 2 });
30
+ }
31
+ let result;
32
+ try {
33
+ result = await data_provider_1.dataService.setSkillVisibility({
34
+ skillId: args.id,
35
+ isPublic: flags.public === true,
36
+ ...(flags.price !== undefined ? { price: flags.price } : {}),
37
+ });
38
+ }
39
+ catch (error) {
40
+ this.handleApiError(error);
41
+ }
42
+ if (flags.json) {
43
+ this.log(JSON.stringify(result, null, 2));
44
+ return;
45
+ }
46
+ this.log(`Set skill ${args.id} visibility: ${flags.public ? 'public' : 'private'}`);
47
+ }
48
+ }
49
+ exports.default = SkillsVisibility;
@@ -11,6 +11,7 @@ export default class StoreAdmissionsApprove extends BaseCommand {
11
11
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
12
12
  };
13
13
  static flags: {
14
+ artifact: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
15
  kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
16
  note: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
16
17
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -13,6 +13,7 @@ class StoreAdmissionsApprove extends base_command_1.BaseCommand {
13
13
  static examples = [
14
14
  '<%= config.bin %> store admissions approve ar_abc123',
15
15
  '<%= config.bin %> store admissions approve skill_abc123 --kind skill',
16
+ '<%= config.bin %> store admissions approve store_release_abc123 --artifact',
16
17
  ];
17
18
  static args = {
18
19
  id: core_1.Args.string({
@@ -22,6 +23,7 @@ class StoreAdmissionsApprove extends base_command_1.BaseCommand {
22
23
  }),
23
24
  };
24
25
  static flags = {
26
+ artifact: core_1.Flags.boolean({ description: 'The id is a Skill or Action artifact release' }),
25
27
  kind: core_1.Flags.string({
26
28
  description: 'The id is a listing, not a release',
27
29
  options: ['skill', 'action'],
@@ -33,9 +35,13 @@ class StoreAdmissionsApprove extends base_command_1.BaseCommand {
33
35
  this.requireAuth();
34
36
  const { args, flags } = await this.parse(StoreAdmissionsApprove);
35
37
  try {
36
- const result = flags.kind
37
- ? await data_provider_1.dataService.approveStoreListingAdmission(flags.kind, args.id, flags.note)
38
- : await data_provider_1.dataService.approveStoreReleaseAdmission(args.id, flags.note);
38
+ if (flags.artifact && flags.kind)
39
+ this.error('Use either --artifact or --kind');
40
+ const result = flags.artifact
41
+ ? await data_provider_1.dataService.approveStoreArtifactAdmission(args.id, flags.note)
42
+ : flags.kind
43
+ ? await data_provider_1.dataService.approveStoreListingAdmission(flags.kind, args.id, flags.note)
44
+ : await data_provider_1.dataService.approveStoreReleaseAdmission(args.id, flags.note);
39
45
  if (flags.json) {
40
46
  this.log(JSON.stringify(result, null, 2));
41
47
  return;
@@ -45,6 +45,12 @@ class StoreAdmissionsList extends base_command_1.BaseCommand {
45
45
  name: l.name || '',
46
46
  facets: (l.admission?.facets || []).join(','),
47
47
  })),
48
+ ...(result.artifacts || []).map((a) => ({
49
+ kind: `${a.entityType} artifact`,
50
+ id: a.releaseId,
51
+ name: `${a.entityId} v${a.versionLabel}`,
52
+ facets: (a.admission?.facets || []).join(','),
53
+ })),
48
54
  ];
49
55
  if (!rows.length) {
50
56
  this.log('Nothing waiting for review.');
@@ -10,6 +10,7 @@ export default class StoreAdmissionsRefuse extends BaseCommand {
10
10
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
11
11
  };
12
12
  static flags: {
13
+ artifact: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
14
  kind: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
15
  note: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
15
16
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -12,6 +12,7 @@ class StoreAdmissionsRefuse extends base_command_1.BaseCommand {
12
12
  static examples = [
13
13
  '<%= config.bin %> store admissions refuse ar_abc123 --note "unbounded network egress"',
14
14
  '<%= config.bin %> store admissions refuse act_abc123 --kind action --note "reason"',
15
+ '<%= config.bin %> store admissions refuse store_release_abc123 --artifact --note "reason"',
15
16
  ];
16
17
  static args = {
17
18
  id: core_1.Args.string({
@@ -21,6 +22,7 @@ class StoreAdmissionsRefuse extends base_command_1.BaseCommand {
21
22
  }),
22
23
  };
23
24
  static flags = {
25
+ artifact: core_1.Flags.boolean({ description: 'The id is a Skill or Action artifact release' }),
24
26
  kind: core_1.Flags.string({
25
27
  description: 'The id is a listing, not a release',
26
28
  options: ['skill', 'action'],
@@ -32,9 +34,13 @@ class StoreAdmissionsRefuse extends base_command_1.BaseCommand {
32
34
  this.requireAuth();
33
35
  const { args, flags } = await this.parse(StoreAdmissionsRefuse);
34
36
  try {
35
- const result = flags.kind
36
- ? await data_provider_1.dataService.refuseStoreListingAdmission(flags.kind, args.id, flags.note || '')
37
- : await data_provider_1.dataService.refuseStoreReleaseAdmission(args.id, flags.note || '');
37
+ if (flags.artifact && flags.kind)
38
+ this.error('Use either --artifact or --kind');
39
+ const result = flags.artifact
40
+ ? await data_provider_1.dataService.refuseStoreArtifactAdmission(args.id, flags.note || '')
41
+ : flags.kind
42
+ ? await data_provider_1.dataService.refuseStoreListingAdmission(flags.kind, args.id, flags.note || '')
43
+ : await data_provider_1.dataService.refuseStoreReleaseAdmission(args.id, flags.note || '');
38
44
  if (flags.json) {
39
45
  this.log(JSON.stringify(result, null, 2));
40
46
  return;
@@ -1,6 +1,8 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ export declare function parsePermissionManifest(value: string): Record<string, string[]>;
2
3
  export default class StoreArtifactsRelease extends BaseCommand {
3
4
  static description: string;
5
+ static examples: string[];
4
6
  static args: {
5
7
  entityType: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
8
  entityId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
@@ -8,6 +10,7 @@ export default class StoreArtifactsRelease extends BaseCommand {
8
10
  static flags: {
9
11
  versionLabel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
12
  artifactRef: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ 'permission-manifest': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
11
14
  price: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
15
  workspaceId: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
16
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -1,10 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePermissionManifest = parsePermissionManifest;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../../base-command");
7
+ const permissionFields = [
8
+ 'tools',
9
+ 'connectors',
10
+ 'dataScopes',
11
+ 'networkDomains',
12
+ 'secretNames',
13
+ ];
14
+ function parsePermissionManifest(value) {
15
+ const parsed = JSON.parse(value);
16
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
17
+ throw new Error('The permission manifest must be an object');
18
+ }
19
+ const manifest = parsed;
20
+ if (Object.keys(manifest).some((key) => !permissionFields.includes(key)) ||
21
+ permissionFields.some((key) => {
22
+ const entries = manifest[key];
23
+ return (!Array.isArray(entries) ||
24
+ !entries.every((entry) => typeof entry === 'string' && entry.trim() === entry && entry.length > 0) ||
25
+ new Set(entries).size !== entries.length);
26
+ })) {
27
+ throw new Error('The permission manifest must explicitly declare five arrays of unique, non-empty strings');
28
+ }
29
+ return manifest;
30
+ }
6
31
  class StoreArtifactsRelease extends base_command_1.BaseCommand {
7
32
  static description = 'Publish an immutable dependency release for your own skill or action';
33
+ static examples = [
34
+ '<%= config.bin %> store artifacts release skill <skill-id> --versionLabel 1.0 --artifactRef skill:<skill-id> --permission-manifest <json>',
35
+ ];
8
36
  static args = {
9
37
  entityType: core_1.Args.string({ required: true, ignoreStdin: true }),
10
38
  entityId: core_1.Args.string({ required: true, ignoreStdin: true }),
@@ -12,6 +40,10 @@ class StoreArtifactsRelease extends base_command_1.BaseCommand {
12
40
  static flags = {
13
41
  versionLabel: core_1.Flags.string({ required: true }),
14
42
  artifactRef: core_1.Flags.string({ required: true }),
43
+ 'permission-manifest': core_1.Flags.string({
44
+ required: true,
45
+ description: 'Explicit JSON declaration of tools, connectors, dataScopes, networkDomains, and secretNames used by this release',
46
+ }),
15
47
  price: core_1.Flags.integer({ description: 'Price in micro-USD; omit for zero-price' }),
16
48
  workspaceId: core_1.Flags.string({ description: 'Publish as this workspace' }),
17
49
  json: core_1.Flags.boolean({ description: 'Output JSON' }),
@@ -19,9 +51,19 @@ class StoreArtifactsRelease extends base_command_1.BaseCommand {
19
51
  async run() {
20
52
  this.requireAuth();
21
53
  const { args, flags } = await this.parse(StoreArtifactsRelease);
54
+ let permissionManifest;
55
+ try {
56
+ permissionManifest = parsePermissionManifest(flags['permission-manifest']);
57
+ }
58
+ catch {
59
+ this.error('--permission-manifest must declare tools, connectors, dataScopes, networkDomains, and secretNames as string arrays', {
60
+ exit: 2,
61
+ });
62
+ }
22
63
  const result = await data_provider_1.dataService.publishStoreArtifactRelease(args.entityType, args.entityId, {
23
64
  versionLabel: flags.versionLabel,
24
65
  artifactRef: flags.artifactRef,
66
+ permissionManifest,
25
67
  ...(flags.price !== undefined ? { price: flags.price } : {}),
26
68
  ...(flags.workspaceId ? { publishAs: { workspaceId: flags.workspaceId } } : {}),
27
69
  });
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /** The buyer's installed copies, including stopped copies and their source releases. */
3
+ export default class StoreInstalls extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ const format_1 = require("../../lib/format");
7
+ /** The buyer's installed copies, including stopped copies and their source releases. */
8
+ class StoreInstalls extends base_command_1.BaseCommand {
9
+ static description = 'List your Store installs and the releases they came from';
10
+ static examples = [
11
+ '<%= config.bin %> store installs',
12
+ '<%= config.bin %> store installs --json',
13
+ ];
14
+ static flags = {
15
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
16
+ };
17
+ async run() {
18
+ this.requireAuth();
19
+ const { flags } = await this.parse(StoreInstalls);
20
+ let response;
21
+ try {
22
+ response = await data_provider_1.dataService.getStoreInstalls();
23
+ }
24
+ catch (error) {
25
+ this.handleApiError(error);
26
+ return;
27
+ }
28
+ const installs = response?.installs ?? [];
29
+ if (flags.json) {
30
+ this.log(JSON.stringify({ object: 'list', data: installs }, null, 2));
31
+ return;
32
+ }
33
+ if (installs.length === 0) {
34
+ this.log('No Store installs found.');
35
+ return;
36
+ }
37
+ (0, format_1.renderTable)(installs.map((install) => ({
38
+ agent: install.agentId,
39
+ copy: install.installedAgentId ?? '-',
40
+ release: install.installedReleaseId ?? '-',
41
+ version: install.installedVersionLabel ?? '-',
42
+ state: install.copyMissing ? 'copy missing' : install.stopReason ? 'stopped' : 'installed',
43
+ reason: install.stopReason ?? '-',
44
+ })), [
45
+ { key: 'agent', header: 'LISTING' },
46
+ { key: 'copy', header: 'YOUR COPY' },
47
+ { key: 'release', header: 'RELEASE' },
48
+ { key: 'version', header: 'VERSION' },
49
+ { key: 'state', header: 'STATE' },
50
+ { key: 'reason', header: 'REASON', maxWidth: 40 },
51
+ ], (line) => this.log(line));
52
+ }
53
+ }
54
+ exports.default = StoreInstalls;
@@ -82,7 +82,7 @@ class StoreUpdates extends base_command_1.BaseCommand {
82
82
  channel: install.updatePolicy ?? 'manual',
83
83
  have,
84
84
  waiting: update.versionLabel ?? '-',
85
- detail: 'applies cleanly',
85
+ detail: 'available; changes and capabilities are checked when you update',
86
86
  });
87
87
  }
88
88
  if (install.stopReason) {
@@ -1,4 +1,14 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * Comments arrive flat. Re-thread them so a reply prints under its parent —
4
+ * the flattened order hid which remark a reply answered (OSK-12743). Each
5
+ * level keeps the page's own order; a reply whose parent is on another page
6
+ * (or deleted) keeps its position as a root rather than vanishing.
7
+ */
8
+ export declare function threadComments(items: Array<Record<string, unknown>>): Array<{
9
+ c: Record<string, unknown>;
10
+ depth: number;
11
+ }>;
2
12
  export default class TasksCommentsList extends BaseCommand {
3
13
  static description: string;
4
14
  static examples: string[];
@@ -1,11 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.threadComments = threadComments;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../../base-command");
6
7
  const format_1 = require("../../../lib/format");
7
8
  const principal_display_1 = require("../../../lib/principal-display");
8
9
  const task_resolver_1 = require("../../../lib/task-resolver");
10
+ /**
11
+ * Comments arrive flat. Re-thread them so a reply prints under its parent —
12
+ * the flattened order hid which remark a reply answered (OSK-12743). Each
13
+ * level keeps the page's own order; a reply whose parent is on another page
14
+ * (or deleted) keeps its position as a root rather than vanishing.
15
+ */
16
+ function threadComments(items) {
17
+ const byId = new Map(items.map((c) => [String(c.id ?? c._id), c]));
18
+ const children = new Map();
19
+ const roots = [];
20
+ for (const c of items) {
21
+ const parentId = c.parentCommentId ? String(c.parentCommentId) : null;
22
+ if (parentId && byId.has(parentId)) {
23
+ const siblings = children.get(parentId) ?? [];
24
+ siblings.push(c);
25
+ children.set(parentId, siblings);
26
+ }
27
+ else {
28
+ roots.push(c);
29
+ }
30
+ }
31
+ const threaded = [];
32
+ const emit = (c, depth) => {
33
+ threaded.push({ c, depth });
34
+ // The list arrives newest-first; inside a thread, oldest→newest reads as
35
+ // the conversation happened.
36
+ for (const child of (children.get(String(c.id ?? c._id)) ?? []).slice().reverse()) {
37
+ emit(child, depth + 1);
38
+ }
39
+ };
40
+ for (const root of roots)
41
+ emit(root, 0);
42
+ return threaded;
43
+ }
9
44
  class TasksCommentsList extends base_command_1.BaseCommand {
10
45
  static description = 'List comments on a task';
11
46
  static examples = [
@@ -72,15 +107,24 @@ class TasksCommentsList extends base_command_1.BaseCommand {
72
107
  // space roster (or the caller's own record) can see them; ids that cannot
73
108
  // resolve print unchanged.
74
109
  const principalNames = await (0, principal_display_1.resolvePrincipalNames)(items.map((c) => c.authorAgentId ?? c.authorUserId ?? c.author));
75
- const rows = items.map((c) => ({
76
- time: (0, format_1.localTime)(c.createdAt),
77
- // The server enriches authors directly (_authorUserName/_authorAgentName);
78
- // the roster lookup is the fallback for a deployment that predates it.
79
- author: String(c._authorUserName ?? c._authorAgentName ?? '') ||
80
- (0, principal_display_1.displayPrincipal)(c.authorAgentId ?? c.authorUserId ?? c.author, principalNames),
81
- body: (0, format_1.truncate)(String(c.body ?? c.content ?? '-'), 60),
82
- id: String(c.id ?? c._id ?? '-'),
83
- }));
110
+ const threaded = threadComments(items);
111
+ const rows = threaded.map(({ c, depth }) => {
112
+ const reactions = c.reactions
113
+ ?.map((r) => `${r.emoji ?? ''}${r.count ?? ''}`)
114
+ .filter(Boolean)
115
+ .join(' ');
116
+ const state = c.status === 'resolved' || c.resolvedAt ? 'resolved' : undefined;
117
+ const suffix = [state, reactions].filter(Boolean).join(' · ');
118
+ return {
119
+ time: (0, format_1.localTime)(c.createdAt),
120
+ // The server enriches authors directly (_authorUserName/_authorAgentName);
121
+ // the roster lookup is the fallback for a deployment that predates it.
122
+ author: String(c._authorUserName ?? c._authorAgentName ?? '') ||
123
+ (0, principal_display_1.displayPrincipal)(c.authorAgentId ?? c.authorUserId ?? c.author, principalNames),
124
+ body: (0, format_1.truncate)(String(c.body ?? c.content ?? '-'), 60) + (suffix ? ` [${suffix}]` : ''),
125
+ id: `${depth > 0 ? ' '.repeat(depth) + '↳ ' : ''}${String(c.id ?? c._id ?? '-')}`,
126
+ };
127
+ });
84
128
  (0, format_1.renderTable)(rows, [
85
129
  { key: 'time', header: 'TIME' },
86
130
  { key: 'author', header: 'AUTHOR', maxWidth: 30 },
@@ -14,7 +14,9 @@ class TasksCommentsReact extends base_command_1.BaseCommand {
14
14
  commentId: core_1.Args.string({ description: 'Comment UUID', required: true, ignoreStdin: true }),
15
15
  };
16
16
  static flags = {
17
- task: core_1.Flags.string({ description: 'Task ID; overrides daemon runtime context' }),
17
+ task: core_1.Flags.string({
18
+ description: 'Task ID; required when not running inside a daemon task session (OSK-12742)',
19
+ }),
18
20
  emoji: core_1.Flags.string({ description: 'Emoji reaction (up to 16 characters)', required: true }),
19
21
  json: core_1.Flags.boolean({ description: 'Output the raw response as JSON' }),
20
22
  };
@@ -1,4 +1,5 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ export declare function defaultDeliverableIdempotencyKey(task: string, payload: Record<string, unknown>): string;
2
3
  /**
3
4
  * `skrr tasks deliverable add <task> <kind>:<identity>[@<location>]`
4
5
  *
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.defaultDeliverableIdempotencyKey = defaultDeliverableIdempotencyKey;
36
37
  const fs = __importStar(require("node:fs"));
37
38
  const path = __importStar(require("node:path"));
38
39
  const core_1 = require("@oclif/core");
@@ -43,6 +44,11 @@ const outbox_1 = require("../../../lib/outbox");
43
44
  const api_fetch_1 = require("../../../lib/api-fetch");
44
45
  const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
45
46
  const tasks_1 = require("../../../lib/tasks");
47
+ function defaultDeliverableIdempotencyKey(task, payload) {
48
+ // A retry of one exact observation keeps its key. A new version, provider,
49
+ // digest, or expectation binding is a different observation.
50
+ return (0, outbox_1.idempotencyKeyFor)('task-deliverable', task, JSON.stringify(payload));
51
+ }
46
52
  /**
47
53
  * `skrr tasks deliverable add <task> <kind>:<identity>[@<location>]`
48
54
  *
@@ -155,6 +161,7 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
155
161
  taskId: args.task,
156
162
  filePath: archivePath,
157
163
  identity: parsed.value.identity,
164
+ kind: parsed.value.kind,
158
165
  title: flags.title,
159
166
  expectationIds: flags.expectation,
160
167
  requestedStatus: status,
@@ -170,7 +177,7 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
170
177
  }
171
178
  let result;
172
179
  try {
173
- const payload = {
180
+ const observation = {
174
181
  ...parsed.value,
175
182
  ...(status ? { status } : {}),
176
183
  title: flags.title,
@@ -179,8 +186,11 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
179
186
  contentDigest: flags['content-digest'],
180
187
  expectationIds: flags.expectation,
181
188
  runId: flags['run-id'],
189
+ };
190
+ const payload = {
191
+ ...observation,
182
192
  idempotencyKey: flags['idempotency-key'] ||
183
- (0, outbox_1.idempotencyKeyFor)('task-deliverable', args.task, JSON.stringify({ coordinate: parsed.value, status })),
193
+ defaultDeliverableIdempotencyKey(args.task, observation),
184
194
  };
185
195
  const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('deliverable');
186
196
  result = sessionPath
@@ -249,6 +259,7 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
249
259
  role: 'deliverable',
250
260
  ledger: {
251
261
  identity: opts.identity,
262
+ kind: opts.kind,
252
263
  title: opts.title,
253
264
  expectationIds: opts.expectationIds,
254
265
  },
@@ -23,6 +23,20 @@ function fidelityLabel(value) {
23
23
  return value || 'unknown fidelity';
24
24
  }
25
25
  }
26
+ /**
27
+ * One coordinate CAN name several pinned versions — v1 and v2 of the same
28
+ * report share title and locator, so without a discriminator the rows look
29
+ * identical (OSK-12798). The content digest is the honest one when pinned;
30
+ * otherwise the version's own id suffix is the distinguisher.
31
+ */
32
+ function versionLabel(item) {
33
+ if (item.contentDigest) {
34
+ const [algo, hex] = item.contentDigest.split(':');
35
+ return `digest ${algo}:${(hex || '').slice(0, 12)}`;
36
+ }
37
+ const id = (item.resourceVersionId || '').replace(/^wrv_/, '');
38
+ return id ? `version ${id.slice(0, 12)}` : 'version unpinned';
39
+ }
26
40
  class TasksDeliverableList extends base_command_1.BaseCommand {
27
41
  static description = 'List Deliverables with exact version, location, and evidence state';
28
42
  static args = {
@@ -51,7 +65,7 @@ class TasksDeliverableList extends base_command_1.BaseCommand {
51
65
  this.log(`${item.linkId} · ${item.kind}${item.format ? `/${item.format}` : ''} · ` +
52
66
  `${item.title || item.identity || '(untitled)'}\n` +
53
67
  ` ${item.location || 'no locator'} · ${fidelityLabel(item.versionFidelity)} · ` +
54
- `${item.status} · ${item.evidenceLevel}`);
68
+ `${versionLabel(item)} · ${item.status} · ${item.evidenceLevel}`);
55
69
  }
56
70
  }
57
71
  }