@ericsanchezok/synergy-util 3.0.24 → 3.1.0

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 (74) hide show
  1. package/LICENSE.codex +201 -0
  2. package/NOTICE +9 -0
  3. package/dist/activity.d.ts +9 -4
  4. package/dist/activity.js +20 -19
  5. package/dist/asset-reference.d.ts +15 -0
  6. package/dist/asset-reference.js +98 -0
  7. package/dist/atomic-file.d.ts +9 -0
  8. package/dist/atomic-file.js +86 -0
  9. package/dist/attachment-presentation.d.ts +26 -0
  10. package/dist/attachment-presentation.js +11 -0
  11. package/dist/capability.d.ts +1 -1
  12. package/dist/capability.js +5 -19
  13. package/dist/cli-command.d.ts +12 -0
  14. package/dist/cli-command.js +3 -0
  15. package/dist/encode.d.ts +3 -0
  16. package/dist/encode.js +7 -0
  17. package/dist/error.d.ts +36 -56
  18. package/dist/feature-config.d.ts +4 -0
  19. package/dist/feature-config.js +23 -0
  20. package/dist/features.d.ts +134 -0
  21. package/dist/features.js +195 -0
  22. package/dist/file-patch.d.ts +31 -0
  23. package/dist/file-patch.js +218 -0
  24. package/dist/filesystem-identity.d.ts +28 -0
  25. package/dist/filesystem-identity.js +71 -0
  26. package/dist/fn.d.ts +1 -1
  27. package/dist/fs-lock.d.ts +5 -1
  28. package/dist/fs-lock.js +14 -6
  29. package/dist/input-assistance.d.ts +51 -0
  30. package/dist/input-assistance.js +61 -0
  31. package/dist/installed-launcher.d.ts +18 -0
  32. package/dist/installed-launcher.js +39 -0
  33. package/dist/io-retry.d.ts +11 -0
  34. package/dist/io-retry.js +36 -0
  35. package/dist/json-value.d.ts +4 -0
  36. package/dist/json-value.js +12 -0
  37. package/dist/markdown-assets.d.ts +6 -0
  38. package/dist/markdown-assets.js +29 -0
  39. package/dist/markdown-link.d.ts +2 -0
  40. package/dist/markdown-link.js +8 -0
  41. package/dist/native-assets.d.ts +7 -0
  42. package/dist/native-assets.js +28 -0
  43. package/dist/network-error.d.ts +7 -0
  44. package/dist/network-error.js +111 -0
  45. package/dist/plugin-policy.d.ts +1 -1
  46. package/dist/process-group.d.ts +45 -0
  47. package/dist/process-group.js +156 -0
  48. package/dist/process-identity.js +5 -2
  49. package/dist/projection-patch.d.ts +9 -0
  50. package/dist/projection-patch.js +74 -0
  51. package/dist/reasoning-item.d.ts +1 -0
  52. package/dist/reasoning-item.js +16 -0
  53. package/dist/render-artifact.d.ts +352 -0
  54. package/dist/render-artifact.js +115 -0
  55. package/dist/render-ui.d.ts +202 -0
  56. package/dist/render-ui.js +208 -0
  57. package/dist/resource-reference.d.ts +73 -0
  58. package/dist/resource-reference.js +227 -0
  59. package/dist/retry.d.ts +4 -0
  60. package/dist/retry.js +56 -23
  61. package/dist/runtime-startup.d.ts +240 -2
  62. package/dist/runtime-startup.js +108 -9
  63. package/dist/terminal.d.ts +217 -0
  64. package/dist/terminal.js +111 -0
  65. package/dist/tool-display.d.ts +10 -0
  66. package/dist/tool-display.js +22 -1
  67. package/dist/tool-timeout.d.ts +21 -0
  68. package/dist/tool-timeout.js +1 -0
  69. package/dist/tool.d.ts +6 -7
  70. package/dist/workbench-view.d.ts +692 -0
  71. package/dist/workbench-view.js +90 -0
  72. package/dist/worker-plan.d.ts +39 -0
  73. package/dist/worker-plan.js +33 -0
  74. package/package.json +28 -4
@@ -27,12 +27,6 @@ export const SYNERGY_CAPABILITY_DETAILS = {
27
27
  description: "Can operate observed native application windows using background input in Full Access mode.",
28
28
  nonBypassable: true,
29
29
  },
30
- shell_read: {
31
- category: "runtime",
32
- severity: "low",
33
- title: "Run read-only shell commands",
34
- description: "Can run shell commands classified as read-only.",
35
- },
36
30
  shell: {
37
31
  category: "runtime",
38
32
  severity: "medium",
@@ -51,13 +45,6 @@ export const SYNERGY_CAPABILITY_DETAILS = {
51
45
  title: "Run remote-write shell commands",
52
46
  description: "Can modify existing remote state outside ordinary branch/PR publication workflows.",
53
47
  },
54
- shell_remote_execute: {
55
- category: "runtime",
56
- severity: "high",
57
- title: "Execute commands on remote host",
58
- description: "Can run bash/process commands on a remote Synergy Link host.",
59
- nonBypassable: true,
60
- },
61
48
  shell_remote_publish: {
62
49
  category: "runtime",
63
50
  severity: "medium",
@@ -68,14 +55,14 @@ export const SYNERGY_CAPABILITY_DETAILS = {
68
55
  category: "runtime",
69
56
  severity: "high",
70
57
  title: "Run destructive shell commands",
71
- description: "Can run commands that may delete, overwrite, or rewrite important local state.",
58
+ description: "Can run commands that the OS sandbox cannot express: host-level or system-location destruction, irreversible local history loss, and privilege escalation.",
72
59
  nonBypassable: true,
73
60
  },
74
61
  shell_hardline: {
75
62
  category: "runtime",
76
63
  severity: "high",
77
64
  title: "Run forbidden shell commands",
78
- description: "Matches shell commands that Synergy treats as a hard safety boundary.",
65
+ description: "Matches the machine-wide commands Synergy refuses under every profile: filesystem-root and home destruction, filesystem-format and power tools, fork bombs, and raw-device writes.",
79
66
  nonBypassable: true,
80
67
  },
81
68
  file_write: {
@@ -315,10 +302,8 @@ export const SYNERGY_CAPABILITY_DETAILS = {
315
302
  export const SYNERGY_PROFILE_CAPABILITIES = [
316
303
  "file_read",
317
304
  "file_write",
318
- "shell_read",
319
305
  "shell",
320
306
  "shell_branch_mutation",
321
- "shell_remote_execute",
322
307
  "shell_remote_publish",
323
308
  "shell_remote_write",
324
309
  "shell_destructive",
@@ -371,6 +356,7 @@ export const SYNERGY_PERMISSION_CAPABILITY = {
371
356
  edit: "file_write",
372
357
  write: "file_write",
373
358
  revise_file: "file_write",
359
+ apply_patch: "file_write",
374
360
  resolve_conflicts: "file_write",
375
361
  save_file: "file_write",
376
362
  bash: "shell",
@@ -381,7 +367,6 @@ export const SYNERGY_PERMISSION_CAPABILITY = {
381
367
  scan_document: "file_read",
382
368
  look_at: "file_read",
383
369
  view_image: "file_read",
384
- attach: "file_read",
385
370
  ast_grep: "file_read",
386
371
  lsp: "file_read",
387
372
  dagread: "file_read",
@@ -390,7 +375,7 @@ export const SYNERGY_PERMISSION_CAPABILITY = {
390
375
  task_output: "file_read",
391
376
  question: "file_read",
392
377
  skill: "file_read",
393
- render: "file_read",
378
+ render: "session_state",
394
379
  agenda_list: "file_read",
395
380
  agenda_logs: "file_read",
396
381
  session_list: "file_read",
@@ -409,6 +394,7 @@ export const SYNERGY_PERMISSION_CAPABILITY = {
409
394
  doom_loop: "session_state",
410
395
  worktree_enter: "file_write",
411
396
  worktree_leave: "file_write",
397
+ worktree_archive: "file_write",
412
398
  agent_config: "config:write",
413
399
  session_data: "session_data",
414
400
  workspace_data: "workspace_data",
@@ -0,0 +1,12 @@
1
+ import type { CommandModule } from "yargs";
2
+ export type { CommandModule } from "yargs";
3
+ export interface CommandEntry {
4
+ command: string | string[];
5
+ describe: string;
6
+ storage?: "maintenance" | ((positionals: readonly (string | number)[]) => "maintenance" | undefined);
7
+ load(): Promise<CommandModule>;
8
+ }
9
+ type WithDoubleDash<T> = T & {
10
+ "--"?: string[];
11
+ };
12
+ export declare function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>): CommandModule<T, WithDoubleDash<U>>;
@@ -0,0 +1,3 @@
1
+ export function cmd(input) {
2
+ return input;
3
+ }
package/dist/encode.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export declare function base64Encode(value: string): string;
2
+ /** RFC 4648 standard base64 (with padding) for opaque labels like data: URLs;
3
+ * unlike base64Encode this output is safe for strict atob decoders. */
4
+ export declare function base64EncodeStandard(value: string): string;
2
5
  export declare function base64Decode(value: string): string;
3
6
  export declare function hash(content: string, algorithm?: string): Promise<string>;
4
7
  export declare function checksum(content: string): string | undefined;
package/dist/encode.js CHANGED
@@ -3,6 +3,13 @@ export function base64Encode(value) {
3
3
  const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
4
4
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
5
5
  }
6
+ /** RFC 4648 standard base64 (with padding) for opaque labels like data: URLs;
7
+ * unlike base64Encode this output is safe for strict atob decoders. */
8
+ export function base64EncodeStandard(value) {
9
+ const bytes = new TextEncoder().encode(value);
10
+ const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
11
+ return btoa(binary);
12
+ }
6
13
  export function base64Decode(value) {
7
14
  const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/"));
8
15
  const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
package/dist/error.d.ts CHANGED
@@ -1,4 +1,16 @@
1
1
  import z from "zod";
2
+ export interface NamedErrorInstance<Name extends string, Data extends z.core.$ZodType> extends NamedError {
3
+ readonly name: Name;
4
+ readonly data: z.input<Data>;
5
+ schema(): z.ZodObject<{
6
+ name: z.ZodLiteral<Name>;
7
+ data: Data;
8
+ }>;
9
+ toObject(): {
10
+ name: Name;
11
+ data: z.input<Data>;
12
+ };
13
+ }
2
14
  export declare abstract class NamedError extends Error {
3
15
  abstract schema(): z.core.$ZodType;
4
16
  abstract toObject(): {
@@ -7,6 +19,7 @@ export declare abstract class NamedError extends Error {
7
19
  };
8
20
  static create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data): {
9
21
  new (data: z.input<Data>, options?: ErrorOptions): {
22
+ cause?: unknown;
10
23
  readonly name: Name;
11
24
  readonly data: z.core.input<Data>;
12
25
  schema(): z.ZodObject<{
@@ -19,18 +32,20 @@ export declare abstract class NamedError extends Error {
19
32
  };
20
33
  message: string;
21
34
  stack?: string;
22
- cause?: unknown;
23
35
  };
36
+ isError(error: unknown): error is Error;
37
+ isError(value: unknown): value is Error;
24
38
  readonly Schema: z.ZodObject<{
25
39
  name: z.ZodLiteral<Name>;
26
40
  data: Data;
27
41
  }, z.core.$strip>;
28
- isInstance(input: any): input is InstanceType</*elided*/ any>;
42
+ isInstance(input: any): input is NamedErrorInstance<Name, Data>;
29
43
  create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data): /*elided*/ any;
30
44
  readonly Unknown: {
31
45
  new (data: {
32
46
  message: string;
33
47
  }, options?: ErrorOptions): {
48
+ cause?: unknown;
34
49
  readonly name: "UnknownError";
35
50
  readonly data: {
36
51
  message: string;
@@ -49,53 +64,35 @@ export declare abstract class NamedError extends Error {
49
64
  };
50
65
  message: string;
51
66
  stack?: string;
52
- cause?: unknown;
53
67
  };
68
+ isError(error: unknown): error is Error;
69
+ isError(value: unknown): value is Error;
54
70
  readonly Schema: z.ZodObject<{
55
71
  name: z.ZodLiteral<"UnknownError">;
56
72
  data: z.ZodObject<{
57
73
  message: z.ZodString;
58
74
  }, z.core.$strip>;
59
75
  }, z.core.$strip>;
60
- isInstance(input: any): input is {
61
- readonly name: "UnknownError";
62
- readonly data: {
63
- message: string;
64
- };
65
- schema(): z.ZodObject<{
66
- name: z.ZodLiteral<"UnknownError">;
67
- data: z.ZodObject<{
68
- message: z.ZodString;
69
- }, z.core.$strip>;
70
- }, z.core.$strip>;
71
- toObject(): {
72
- name: "UnknownError";
73
- data: {
74
- message: string;
75
- };
76
- };
77
- message: string;
78
- stack?: string;
79
- cause?: unknown;
80
- };
76
+ isInstance(input: any): input is NamedErrorInstance<"UnknownError", z.ZodObject<{
77
+ message: z.ZodString;
78
+ }, z.core.$strip>>;
81
79
  create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data): /*elided*/ any;
82
80
  readonly Unknown: /*elided*/ any;
83
- isError(value: unknown): value is Error;
84
81
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
85
82
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
83
+ prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
86
84
  stackTraceLimit: number;
87
- prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
88
85
  };
89
- isError(value: unknown): value is Error;
90
86
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
91
87
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
88
+ prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
92
89
  stackTraceLimit: number;
93
- prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
94
90
  };
95
91
  static readonly Unknown: {
96
92
  new (data: {
97
93
  message: string;
98
94
  }, options?: ErrorOptions): {
95
+ cause?: unknown;
99
96
  readonly name: "UnknownError";
100
97
  readonly data: {
101
98
  message: string;
@@ -114,37 +111,21 @@ export declare abstract class NamedError extends Error {
114
111
  };
115
112
  message: string;
116
113
  stack?: string;
117
- cause?: unknown;
118
114
  };
115
+ isError(error: unknown): error is Error;
116
+ isError(value: unknown): value is Error;
119
117
  readonly Schema: z.ZodObject<{
120
118
  name: z.ZodLiteral<"UnknownError">;
121
119
  data: z.ZodObject<{
122
120
  message: z.ZodString;
123
121
  }, z.core.$strip>;
124
122
  }, z.core.$strip>;
125
- isInstance(input: any): input is {
126
- readonly name: "UnknownError";
127
- readonly data: {
128
- message: string;
129
- };
130
- schema(): z.ZodObject<{
131
- name: z.ZodLiteral<"UnknownError">;
132
- data: z.ZodObject<{
133
- message: z.ZodString;
134
- }, z.core.$strip>;
135
- }, z.core.$strip>;
136
- toObject(): {
137
- name: "UnknownError";
138
- data: {
139
- message: string;
140
- };
141
- };
142
- message: string;
143
- stack?: string;
144
- cause?: unknown;
145
- };
123
+ isInstance(input: any): input is NamedErrorInstance<"UnknownError", z.ZodObject<{
124
+ message: z.ZodString;
125
+ }, z.core.$strip>>;
146
126
  create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data): {
147
127
  new (data: z.input<Data>, options?: ErrorOptions): {
128
+ cause?: unknown;
148
129
  readonly name: Name;
149
130
  readonly data: z.core.input<Data>;
150
131
  schema(): z.ZodObject<{
@@ -157,26 +138,25 @@ export declare abstract class NamedError extends Error {
157
138
  };
158
139
  message: string;
159
140
  stack?: string;
160
- cause?: unknown;
161
141
  };
142
+ isError(error: unknown): error is Error;
143
+ isError(value: unknown): value is Error;
162
144
  readonly Schema: z.ZodObject<{
163
145
  name: z.ZodLiteral<Name>;
164
146
  data: Data;
165
147
  }, z.core.$strip>;
166
- isInstance(input: any): input is InstanceType</*elided*/ any>;
148
+ isInstance(input: any): input is NamedErrorInstance<Name, Data>;
167
149
  create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data): /*elided*/ any;
168
150
  readonly Unknown: /*elided*/ any;
169
- isError(value: unknown): value is Error;
170
151
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
171
152
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
153
+ prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
172
154
  stackTraceLimit: number;
173
- prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
174
155
  };
175
156
  readonly Unknown: /*elided*/ any;
176
- isError(value: unknown): value is Error;
177
157
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
178
158
  captureStackTrace(targetObject: object, constructorOpt?: Function): void;
159
+ prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
179
160
  stackTraceLimit: number;
180
- prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
181
161
  };
182
162
  }
@@ -0,0 +1,4 @@
1
+ import { FeaturePreferences } from "./features";
2
+ export declare const FEATURE_CONFIG_FILENAME = "140-features.jsonc";
3
+ export declare function parseFeatureConfig(text: string): FeaturePreferences;
4
+ export declare function readFeaturePreferences(configDirectory: string): Promise<FeaturePreferences>;
@@ -0,0 +1,23 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { parse } from "jsonc-parser";
4
+ import { z } from "zod";
5
+ import { FeaturePreferences } from "./features";
6
+ export const FEATURE_CONFIG_FILENAME = "140-features.jsonc";
7
+ const Document = z.object({ $schema: z.string().optional(), features: FeaturePreferences.optional() }).strict();
8
+ export function parseFeatureConfig(text) {
9
+ const errors = [];
10
+ const value = parse(text, errors, { allowTrailingComma: true });
11
+ if (errors.length)
12
+ throw new Error("Invalid experimental feature configuration");
13
+ return Document.parse(value).features ?? { optIn: [] };
14
+ }
15
+ export async function readFeaturePreferences(configDirectory) {
16
+ const filename = path.join(configDirectory, "synergy.d", FEATURE_CONFIG_FILENAME);
17
+ const text = await readFile(filename, "utf8").catch((error) => {
18
+ if (error.code === "ENOENT")
19
+ return undefined;
20
+ throw error;
21
+ });
22
+ return text === undefined ? { optIn: [] } : parseFeatureConfig(text);
23
+ }
@@ -0,0 +1,134 @@
1
+ import { z } from "zod";
2
+ export declare const FeatureDefinition: z.ZodObject<{
3
+ id: z.ZodString;
4
+ owner: z.ZodString;
5
+ maturity: z.ZodEnum<{
6
+ experimental: "experimental";
7
+ stable: "stable";
8
+ }>;
9
+ activation: z.ZodEnum<{
10
+ live: "live";
11
+ restart: "restart";
12
+ }>;
13
+ name: z.ZodString;
14
+ description: z.ZodString;
15
+ component: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$strict>;
17
+ export type FeatureDefinition = z.infer<typeof FeatureDefinition>;
18
+ export declare const ComponentFeatures: z.ZodObject<{
19
+ kind: z.ZodEnum<{
20
+ bridge: "bridge";
21
+ foundation: "foundation";
22
+ product: "product";
23
+ }>;
24
+ definitions: z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ owner: z.ZodString;
27
+ maturity: z.ZodEnum<{
28
+ experimental: "experimental";
29
+ stable: "stable";
30
+ }>;
31
+ activation: z.ZodEnum<{
32
+ live: "live";
33
+ restart: "restart";
34
+ }>;
35
+ name: z.ZodString;
36
+ description: z.ZodString;
37
+ component: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strict>>;
39
+ }, z.core.$strict>;
40
+ export type ComponentFeatures = z.infer<typeof ComponentFeatures>;
41
+ export declare const FeaturePreferences: z.ZodObject<{
42
+ optIn: z.ZodArray<z.ZodString>;
43
+ }, z.core.$strict>;
44
+ export type FeaturePreferences = z.infer<typeof FeaturePreferences>;
45
+ export interface FeatureComponent {
46
+ readonly id: string;
47
+ readonly version: string;
48
+ readonly requires?: Readonly<Record<string, string>>;
49
+ readonly features?: {
50
+ readonly kind: ComponentFeatures["kind"];
51
+ readonly definitions: readonly FeatureDefinition[];
52
+ };
53
+ }
54
+ export declare const FeaturePlan: z.ZodObject<{
55
+ definitions: z.ZodArray<z.ZodObject<{
56
+ id: z.ZodString;
57
+ owner: z.ZodString;
58
+ maturity: z.ZodEnum<{
59
+ experimental: "experimental";
60
+ stable: "stable";
61
+ }>;
62
+ activation: z.ZodEnum<{
63
+ live: "live";
64
+ restart: "restart";
65
+ }>;
66
+ name: z.ZodString;
67
+ description: z.ZodString;
68
+ component: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strict>>;
70
+ optIn: z.ZodArray<z.ZodString>;
71
+ selection: z.ZodArray<z.ZodObject<{
72
+ id: z.ZodString;
73
+ version: z.ZodString;
74
+ }, z.core.$strict>>;
75
+ components: z.ZodArray<z.ZodString>;
76
+ requirements: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
77
+ }, z.core.$strict>;
78
+ export type FeaturePlan = z.infer<typeof FeaturePlan>;
79
+ export declare const FeatureStatus: z.ZodObject<{
80
+ id: z.ZodString;
81
+ definition: z.ZodOptional<z.ZodObject<{
82
+ id: z.ZodString;
83
+ owner: z.ZodString;
84
+ maturity: z.ZodEnum<{
85
+ experimental: "experimental";
86
+ stable: "stable";
87
+ }>;
88
+ activation: z.ZodEnum<{
89
+ live: "live";
90
+ restart: "restart";
91
+ }>;
92
+ name: z.ZodString;
93
+ description: z.ZodString;
94
+ component: z.ZodOptional<z.ZodString>;
95
+ }, z.core.$strict>>;
96
+ selected: z.ZodBoolean;
97
+ available: z.ZodBoolean;
98
+ enabled: z.ZodBoolean;
99
+ pendingRestart: z.ZodBoolean;
100
+ reason: z.ZodOptional<z.ZodEnum<{
101
+ "component-unselected": "component-unselected";
102
+ "dependency-unavailable": "dependency-unavailable";
103
+ "not-installed": "not-installed";
104
+ "restart-required": "restart-required";
105
+ }>>;
106
+ }, z.core.$strip>;
107
+ export type FeatureStatus = z.infer<typeof FeatureStatus>;
108
+ export declare namespace FeatureSelection {
109
+ function complete(candidates: readonly FeatureComponent[], provided?: Readonly<Record<string, string>>): {
110
+ definitions: {
111
+ id: string;
112
+ owner: string;
113
+ maturity: "experimental" | "stable";
114
+ activation: "live" | "restart";
115
+ name: string;
116
+ description: string;
117
+ component?: string | undefined;
118
+ }[];
119
+ optIn: string[];
120
+ selection: {
121
+ id: string;
122
+ version: string;
123
+ }[];
124
+ components: string[];
125
+ requirements: Record<string, string[]>;
126
+ };
127
+ interface Options {
128
+ optIn?: readonly string[];
129
+ selection?: Readonly<Record<string, string>>;
130
+ provided?: Readonly<Record<string, string>>;
131
+ }
132
+ function resolve(candidates: readonly FeatureComponent[], options?: Options): FeaturePlan;
133
+ function status(plan: FeaturePlan, requested: readonly string[], effective: readonly string[]): FeatureStatus[];
134
+ }
@@ -0,0 +1,195 @@
1
+ import { z } from "zod";
2
+ const Id = z.string().regex(/^[a-z][a-z0-9.-]*$/);
3
+ export const FeatureDefinition = z
4
+ .object({
5
+ id: Id,
6
+ owner: Id,
7
+ maturity: z.enum(["experimental", "stable"]),
8
+ activation: z.enum(["live", "restart"]),
9
+ name: z.string().min(1),
10
+ description: z.string().min(1),
11
+ component: Id.optional(),
12
+ })
13
+ .strict()
14
+ .superRefine((value, context) => {
15
+ if (value.component && value.activation !== "restart")
16
+ context.addIssue({ code: "custom", message: "Component features require restart activation" });
17
+ })
18
+ .meta({ ref: "FeatureDefinition" });
19
+ export const ComponentFeatures = z
20
+ .object({
21
+ kind: z.enum(["foundation", "product", "bridge"]),
22
+ definitions: z.array(FeatureDefinition),
23
+ })
24
+ .strict();
25
+ export const FeaturePreferences = z
26
+ .object({ optIn: z.array(Id).refine((ids) => new Set(ids).size === ids.length, "Duplicate experimental feature") })
27
+ .strict();
28
+ export const FeaturePlan = z
29
+ .object({
30
+ definitions: z.array(FeatureDefinition),
31
+ optIn: FeaturePreferences.shape.optIn,
32
+ selection: z.array(z.object({ id: Id, version: z.string().min(1) }).strict()),
33
+ components: z.array(Id),
34
+ requirements: z.record(Id, z.array(Id)),
35
+ })
36
+ .strict()
37
+ .refine((plan) => {
38
+ const active = activate(plan, plan.optIn);
39
+ return (plan.components.length === active.size &&
40
+ new Set(plan.components).size === active.size &&
41
+ plan.components.every((id) => active.has(id)));
42
+ }, "Active components do not match feature selection");
43
+ export const FeatureStatus = z
44
+ .object({
45
+ id: Id,
46
+ definition: FeatureDefinition.optional(),
47
+ selected: z.boolean(),
48
+ available: z.boolean(),
49
+ enabled: z.boolean(),
50
+ pendingRestart: z.boolean(),
51
+ reason: z.enum(["not-installed", "component-unselected", "dependency-unavailable", "restart-required"]).optional(),
52
+ })
53
+ .meta({ ref: "FeatureStatus" });
54
+ function activate(plan, optIn) {
55
+ const active = new Set(plan.selection.map((component) => component.id));
56
+ for (const definition of plan.definitions) {
57
+ if (definition.component && definition.maturity === "experimental" && !optIn.includes(definition.id))
58
+ active.delete(definition.component);
59
+ }
60
+ let changed = true;
61
+ while (changed) {
62
+ changed = false;
63
+ for (const id of active) {
64
+ if (!(plan.requirements[id] ?? []).some((required) => !active.has(required)))
65
+ continue;
66
+ active.delete(id);
67
+ changed = true;
68
+ }
69
+ }
70
+ return active;
71
+ }
72
+ export var FeatureSelection;
73
+ (function (FeatureSelection) {
74
+ function complete(candidates, provided) {
75
+ return resolve(candidates, {
76
+ provided,
77
+ optIn: candidates.flatMap((component) => component.features?.definitions
78
+ .filter((feature) => feature.maturity === "experimental")
79
+ .map((feature) => feature.id) ?? []),
80
+ });
81
+ }
82
+ FeatureSelection.complete = complete;
83
+ function resolve(candidates, options = {}) {
84
+ const optIn = FeaturePreferences.parse({ optIn: options.optIn ?? [] }).optIn;
85
+ const byID = new Map();
86
+ const definitions = new Map();
87
+ const gates = new Map();
88
+ for (const candidate of candidates) {
89
+ if (byID.has(candidate.id))
90
+ throw new Error(`Duplicate component: ${candidate.id}`);
91
+ byID.set(candidate.id, candidate);
92
+ if (!candidate.features)
93
+ continue;
94
+ const metadata = ComponentFeatures.parse(candidate.features);
95
+ if (metadata.kind === "product" && !metadata.definitions.length)
96
+ throw new Error(`Product component ${candidate.id} needs a feature declaration`);
97
+ for (const definition of metadata.definitions) {
98
+ if (definition.owner !== candidate.id || (definition.component && definition.component !== candidate.id))
99
+ throw new Error(`Feature ${definition.id} must belong to ${candidate.id}`);
100
+ if (definitions.has(definition.id))
101
+ throw new Error(`Duplicate feature: ${definition.id}`);
102
+ definitions.set(definition.id, definition);
103
+ if (!definition.component)
104
+ continue;
105
+ if (gates.has(definition.component))
106
+ throw new Error(`Multiple feature gates for ${definition.component}`);
107
+ gates.set(definition.component, definition);
108
+ }
109
+ }
110
+ for (const candidate of candidates) {
111
+ if (candidate.features?.kind === "bridge" || gates.get(candidate.id)?.maturity === "experimental")
112
+ continue;
113
+ const visited = new Set();
114
+ const check = (required) => {
115
+ if (visited.has(required))
116
+ return;
117
+ visited.add(required);
118
+ if (gates.get(required)?.maturity === "experimental")
119
+ throw new Error(`Component ${candidate.id} requires experimental ${required}; split an optional integration`);
120
+ for (const dependency of Object.keys(byID.get(required)?.requires ?? {}))
121
+ check(dependency);
122
+ };
123
+ for (const required of Object.keys(candidate.requires ?? {}))
124
+ check(required);
125
+ }
126
+ const available = new Set();
127
+ const include = (id) => {
128
+ if (available.has(id) || options.provided?.[id])
129
+ return;
130
+ const candidate = byID.get(id);
131
+ if (!candidate)
132
+ throw new Error(`Component is not installed: ${id}`);
133
+ available.add(id);
134
+ if (candidate.features?.kind === "bridge")
135
+ return;
136
+ for (const required of Object.keys(candidate.requires ?? {}))
137
+ include(required);
138
+ };
139
+ for (const id of options.selection ? Object.keys(options.selection) : byID.keys()) {
140
+ include(id);
141
+ const version = byID.get(id)?.version ?? options.provided?.[id];
142
+ if (options.selection && options.selection[id] !== version)
143
+ throw new Error(`Component ${id} requires exact version ${options.selection[id]}; installed ${version}`);
144
+ }
145
+ const selected = candidates.filter((candidate) => available.has(candidate.id));
146
+ const plan = {
147
+ definitions: [...definitions.values()],
148
+ optIn,
149
+ selection: selected.map(({ id, version }) => ({ id, version })),
150
+ requirements: Object.fromEntries(selected.map((candidate) => [
151
+ candidate.id,
152
+ Object.keys(candidate.requires ?? {}).filter((id) => !options.provided?.[id]),
153
+ ])),
154
+ };
155
+ const active = activate(plan, optIn);
156
+ for (const candidate of selected) {
157
+ if (active.has(candidate.id) ||
158
+ candidate.features?.kind === "bridge" ||
159
+ gates.get(candidate.id)?.maturity === "experimental")
160
+ continue;
161
+ throw new Error(`Component ${candidate.id} requires an unavailable dependency; split an optional integration`);
162
+ }
163
+ return { ...plan, components: [...active] };
164
+ }
165
+ FeatureSelection.resolve = resolve;
166
+ function status(plan, requested, effective) {
167
+ const definitions = new Map(plan.definitions.map((definition) => [definition.id, definition]));
168
+ const owners = new Set(plan.selection.map((component) => component.id));
169
+ const next = activate(plan, requested);
170
+ return [...new Set([...definitions.keys(), ...requested])].map((id) => {
171
+ const definition = definitions.get(id);
172
+ const selected = definition?.maturity === "stable" || requested.includes(id);
173
+ const ownerDisabled = !!definition && !definition.component && !next.has(definition.owner);
174
+ const dependencyUnavailable = !!definition && (plan.requirements[definition.owner] ?? []).some((id) => !next.has(id));
175
+ const available = !!definition && owners.has(definition.owner) && !dependencyUnavailable && !ownerDisabled;
176
+ const enabled = !!definition && effective.includes(id);
177
+ const nextEnabled = !!definition && next.has(definition.owner) && selected;
178
+ const pendingRestart = !!definition &&
179
+ enabled !== nextEnabled &&
180
+ (definition.activation === "restart" ||
181
+ plan.components.includes(definition.owner) !== next.has(definition.owner));
182
+ const reason = !definition
183
+ ? "not-installed"
184
+ : !owners.has(definition.owner)
185
+ ? "component-unselected"
186
+ : dependencyUnavailable || ownerDisabled
187
+ ? "dependency-unavailable"
188
+ : pendingRestart
189
+ ? "restart-required"
190
+ : undefined;
191
+ return { id, definition, selected, available, enabled, pendingRestart, ...(reason ? { reason } : {}) };
192
+ });
193
+ }
194
+ FeatureSelection.status = status;
195
+ })(FeatureSelection || (FeatureSelection = {}));