@vimhead.dev/norn-cli 0.1.0-tip.35614826665.1 → 0.1.0-tip.35724621678.1

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 (119) hide show
  1. package/assets/README.md +41 -13
  2. package/assets/docs/README.md +16 -16
  3. package/assets/docs/agents.md +23 -15
  4. package/assets/docs/cli.md +10 -8
  5. package/assets/docs/composition.md +7 -7
  6. package/assets/docs/persistence.md +22 -22
  7. package/assets/docs/projects.md +6 -6
  8. package/assets/docs/recovery.md +10 -10
  9. package/assets/docs/schemas.md +42 -36
  10. package/assets/docs/workflows.md +39 -32
  11. package/assets/examples/agent-then-analysis/input.json +3 -3
  12. package/assets/examples/agent-then-analysis/norn.project.json +2 -2
  13. package/assets/examples/agent-then-analysis/plugin.ts +37 -9
  14. package/assets/examples/caller-owned-routing/README.md +6 -6
  15. package/assets/examples/caller-owned-routing/assessment.ts +18 -4
  16. package/assets/examples/caller-owned-routing/contracts.ts +7 -2
  17. package/assets/examples/caller-owned-routing/input.json +12 -12
  18. package/assets/examples/caller-owned-routing/norn.project.json +2 -2
  19. package/assets/examples/caller-owned-routing/revision.ts +7 -2
  20. package/assets/examples/caller-owned-routing/router.ts +14 -3
  21. package/assets/examples/caller-selected-continuation/caller.ts +38 -12
  22. package/assets/examples/caller-selected-continuation/input.json +7 -7
  23. package/assets/examples/caller-selected-continuation/norn.project.json +2 -2
  24. package/assets/examples/caller-selected-continuation/producer.ts +10 -3
  25. package/assets/examples/coordinating-multiple-agents/README.md +6 -6
  26. package/assets/examples/coordinating-multiple-agents/input.json +20 -8
  27. package/assets/examples/coordinating-multiple-agents/norn.project.json +2 -2
  28. package/assets/examples/coordinating-multiple-agents/plugin.ts +165 -46
  29. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +30 -7
  30. package/assets/examples/coordinating-multiple-agents/work-queue.ts +211 -63
  31. package/assets/examples/getting-started/norn.project.json +2 -2
  32. package/assets/examples/getting-started/plugin.ts +33 -13
  33. package/assets/examples/minimal-workflow/norn.project.json +2 -2
  34. package/assets/examples/minimal-workflow/plugin.ts +12 -3
  35. package/assets/examples/shared-state/README.md +6 -6
  36. package/assets/examples/shared-state/input.json +1 -1
  37. package/assets/examples/shared-state/norn.project.json +2 -2
  38. package/assets/examples/shared-state/plugin.ts +44 -15
  39. package/assets/examples/shared-state/shared-state.ts +46 -13
  40. package/assets/examples/shared-state/state-tools.ts +68 -16
  41. package/assets/examples/worktree-development-loop/README.md +12 -12
  42. package/assets/examples/worktree-development-loop/norn.project.json +7 -7
  43. package/assets/examples/worktree-development-loop/plugin.ts +7 -1
  44. package/assets/examples/worktree-development-loop/scope.ts +4 -1
  45. package/assets/examples/worktree-development-loop/shared/commands.ts +6 -2
  46. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +18 -4
  47. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +4 -1
  48. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +11 -1
  49. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +6 -2
  50. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +40 -10
  51. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +5 -1
  52. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  53. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +5 -1
  54. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +33 -9
  55. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +51 -8
  56. package/assets/package.json +1 -1
  57. package/assets/packages/cli/src/build-info.ts +4 -1
  58. package/assets/packages/cli/src/bun/cli.ts +21 -5
  59. package/assets/packages/cli/src/cli.ts +585 -170
  60. package/assets/packages/cli/src/client.ts +194 -36
  61. package/assets/packages/cli/src/documentation.ts +135 -30
  62. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  63. package/assets/packages/cli/src/internal/agent-directory.ts +7 -2
  64. package/assets/packages/cli/src/internal/agent-response-tool.ts +57 -15
  65. package/assets/packages/cli/src/internal/agents.ts +248 -60
  66. package/assets/packages/cli/src/internal/commands.ts +82 -20
  67. package/assets/packages/cli/src/internal/documentation-bundle.ts +44 -13
  68. package/assets/packages/cli/src/internal/engine.ts +396 -87
  69. package/assets/packages/cli/src/internal/errors.ts +13 -5
  70. package/assets/packages/cli/src/internal/execution-context.ts +20 -12
  71. package/assets/packages/cli/src/internal/file-coordinator.ts +95 -26
  72. package/assets/packages/cli/src/internal/file-names.ts +4 -1
  73. package/assets/packages/cli/src/internal/launch-request.ts +74 -22
  74. package/assets/packages/cli/src/internal/logs.ts +10 -2
  75. package/assets/packages/cli/src/internal/metrics.ts +247 -61
  76. package/assets/packages/cli/src/internal/pi-assets.ts +105 -23
  77. package/assets/packages/cli/src/internal/run-lease.ts +72 -22
  78. package/assets/packages/cli/src/internal/run-log.ts +49 -20
  79. package/assets/packages/cli/src/internal/run-names.ts +120 -12
  80. package/assets/packages/cli/src/internal/run-state.ts +306 -71
  81. package/assets/packages/cli/src/internal/run-store.ts +216 -53
  82. package/assets/packages/cli/src/internal/usage.ts +23 -7
  83. package/assets/packages/cli/src/internal/worker-directory.ts +9 -3
  84. package/assets/packages/cli/src/internal/workflow-registry.ts +281 -70
  85. package/assets/packages/cli/src/internal/working-directory.ts +2 -1
  86. package/assets/packages/cli/src/workflow-loader.ts +341 -74
  87. package/assets/packages/core/src/atomic-files.ts +13 -4
  88. package/assets/packages/core/src/workflow-transition.ts +13 -2
  89. package/assets/packages/sdk/src/api.ts +248 -80
  90. package/assets/packages/sdk/src/index.ts +6 -1
  91. package/assets/packages/sdk/src/schema.ts +75 -21
  92. package/assets/tests/workflow-ref.test.ts +242 -57
  93. package/bin/norn.mjs +13 -4
  94. package/dist/cli.js +344 -90
  95. package/dist/client.js +100 -22
  96. package/dist/documentation.js +102 -25
  97. package/dist/generated-build-info.d.ts +2 -2
  98. package/dist/generated-build-info.js +2 -2
  99. package/dist/internal/agent-directory.js +3 -1
  100. package/dist/internal/agent-response-tool.js +42 -11
  101. package/dist/internal/agents.js +149 -37
  102. package/dist/internal/commands.js +65 -16
  103. package/dist/internal/documentation-bundle.js +23 -11
  104. package/dist/internal/engine.js +304 -66
  105. package/dist/internal/errors.js +4 -2
  106. package/dist/internal/execution-context.js +2 -1
  107. package/dist/internal/file-coordinator.js +59 -19
  108. package/dist/internal/launch-request.js +32 -14
  109. package/dist/internal/metrics.js +138 -38
  110. package/dist/internal/pi-assets.js +70 -19
  111. package/dist/internal/run-lease.js +29 -9
  112. package/dist/internal/run-log.js +24 -12
  113. package/dist/internal/run-state.js +172 -48
  114. package/dist/internal/run-store.js +157 -45
  115. package/dist/internal/worker-directory.js +6 -2
  116. package/dist/internal/workflow-registry.js +123 -32
  117. package/dist/internal/working-directory.js +2 -1
  118. package/dist/workflow-loader.js +198 -41
  119. package/package.json +2 -2
@@ -31,7 +31,10 @@ export class NornRunLease {
31
31
  private readonly processOwner: NornRunProcessOwner,
32
32
  ) {}
33
33
 
34
- static async acquire(runRoot: string, processOwner: NornRunProcessOwner = currentRunProcessOwner()): Promise<NornRunLease> {
34
+ static async acquire(
35
+ runRoot: string,
36
+ processOwner: NornRunProcessOwner = currentRunProcessOwner(),
37
+ ): Promise<NornRunLease> {
35
38
  const lockRoot = join(runRoot, LOCK_DIR_NAME);
36
39
  for (let attempt = 1; attempt <= ACQUIRE_ATTEMPTS; attempt++) {
37
40
  try {
@@ -48,7 +51,9 @@ export class NornRunLease {
48
51
  } catch (error) {
49
52
  if (!isNodeError(error) || error.code !== "EEXIST") throw error;
50
53
  const owner = await readRunLeaseOwner(join(lockRoot, OWNER_FILE_NAME));
51
- const isStale = owner ? isRunLeaseStale(owner) : await isRunLeaseDirectoryStale(lockRoot);
54
+ const isStale = owner
55
+ ? isRunLeaseStale(owner)
56
+ : await isRunLeaseDirectoryStale(lockRoot);
52
57
  if (!isStale) throw new Error(`Run is already active: ${runRoot}`);
53
58
  await rm(lockRoot, { recursive: true, force: true });
54
59
  }
@@ -59,7 +64,8 @@ export class NornRunLease {
59
64
  async assertOwned(): Promise<void> {
60
65
  if (this.isReleased) throw new Error("Run lease has been released");
61
66
  const owner = await readRunLeaseOwner(this.ownerPath);
62
- if (!owner || owner.token !== this.token) throw new Error("Run lease was lost");
67
+ if (!owner || owner.token !== this.token)
68
+ throw new Error("Run lease was lost");
63
69
  }
64
70
 
65
71
  async release(): Promise<void> {
@@ -68,7 +74,8 @@ export class NornRunLease {
68
74
  if (this.heartbeat) clearInterval(this.heartbeat);
69
75
  await this.heartbeatChain.catch(() => undefined);
70
76
  const owner = await readRunLeaseOwner(this.ownerPath);
71
- if (owner?.token === this.token) await rm(this.lockRoot, { recursive: true, force: true });
77
+ if (owner?.token === this.token)
78
+ await rm(this.lockRoot, { recursive: true, force: true });
72
79
  }
73
80
 
74
81
  private get ownerPath(): string {
@@ -77,10 +84,12 @@ export class NornRunLease {
77
84
 
78
85
  private startHeartbeat(): void {
79
86
  this.heartbeat = setInterval(() => {
80
- this.heartbeatChain = this.heartbeatChain.then(() => this.refreshHeartbeat()).catch(() => {
81
- this.isReleased = true;
82
- if (this.heartbeat) clearInterval(this.heartbeat);
83
- });
87
+ this.heartbeatChain = this.heartbeatChain
88
+ .then(() => this.refreshHeartbeat())
89
+ .catch(() => {
90
+ this.isReleased = true;
91
+ if (this.heartbeat) clearInterval(this.heartbeat);
92
+ });
84
93
  }, HEARTBEAT_INTERVAL_MS);
85
94
  this.heartbeat.unref?.();
86
95
  }
@@ -88,13 +97,21 @@ export class NornRunLease {
88
97
  private async refreshHeartbeat(): Promise<void> {
89
98
  if (this.isReleased) return;
90
99
  const owner = await readRunLeaseOwner(this.ownerPath);
91
- if (!owner || owner.token !== this.token) throw new Error("Run lease was lost");
100
+ if (!owner || owner.token !== this.token)
101
+ throw new Error("Run lease was lost");
92
102
  await this.writeOwner({ acquiredAt: owner.acquiredAt });
93
103
  }
94
104
 
95
- private async writeOwner(input: { readonly acquiredAt: string }): Promise<void> {
105
+ private async writeOwner(input: {
106
+ readonly acquiredAt: string;
107
+ }): Promise<void> {
96
108
  const now = new Date().toISOString();
97
- await writeJsonAtomically(this.ownerPath, { token: this.token, acquiredAt: input.acquiredAt, heartbeatAt: now, ...this.processOwner });
109
+ await writeJsonAtomically(this.ownerPath, {
110
+ token: this.token,
111
+ acquiredAt: input.acquiredAt,
112
+ heartbeatAt: now,
113
+ ...this.processOwner,
114
+ });
98
115
  }
99
116
  }
100
117
 
@@ -104,14 +121,18 @@ export type NornRunProcessOwner = {
104
121
  readonly command: readonly string[];
105
122
  };
106
123
 
107
- export async function getRunLeaseHealth(runRoot: string): Promise<NornRunHealth> {
124
+ export async function getRunLeaseHealth(
125
+ runRoot: string,
126
+ ): Promise<NornRunHealth> {
108
127
  const lockRoot = join(runRoot, LOCK_DIR_NAME);
109
128
  const owner = await readRunLeaseOwner(join(lockRoot, OWNER_FILE_NAME));
110
129
  if (owner) return isRunLeaseStale(owner) ? "unhealthy" : "healthy";
111
- return await isRunLeaseDirectoryStale(lockRoot) ? "unhealthy" : "healthy";
130
+ return (await isRunLeaseDirectoryStale(lockRoot)) ? "unhealthy" : "healthy";
112
131
  }
113
132
 
114
- export async function getRunLeaseOwner(runRoot: string): Promise<NornRunLeaseOwner | undefined> {
133
+ export async function getRunLeaseOwner(
134
+ runRoot: string,
135
+ ): Promise<NornRunLeaseOwner | undefined> {
115
136
  return readRunLeaseOwner(join(runRoot, LOCK_DIR_NAME, OWNER_FILE_NAME));
116
137
  }
117
138
 
@@ -123,7 +144,9 @@ function currentRunProcessOwner(): NornRunProcessOwner {
123
144
  };
124
145
  }
125
146
 
126
- async function readRunLeaseOwner(path: string): Promise<NornRunLeaseOwner | undefined> {
147
+ async function readRunLeaseOwner(
148
+ path: string,
149
+ ): Promise<NornRunLeaseOwner | undefined> {
127
150
  try {
128
151
  return parseRunLeaseOwner(JSON.parse(await readFile(path, "utf8")));
129
152
  } catch (error) {
@@ -135,13 +158,40 @@ async function readRunLeaseOwner(path: string): Promise<NornRunLeaseOwner | unde
135
158
  function parseRunLeaseOwner(value: unknown): NornRunLeaseOwner | undefined {
136
159
  if (!value || typeof value !== "object") return undefined;
137
160
  const owner = value as Partial<NornRunLeaseOwner>;
138
- if (typeof owner.token !== "string" || owner.token.length === 0) return undefined;
139
- if (typeof owner.acquiredAt !== "string" || Number.isNaN(Date.parse(owner.acquiredAt))) return undefined;
140
- if (typeof owner.heartbeatAt !== "string" || Number.isNaN(Date.parse(owner.heartbeatAt))) return undefined;
141
- const pid = typeof owner.pid === "number" && Number.isInteger(owner.pid) ? owner.pid : 0;
142
- const processGroupId = typeof owner.processGroupId === "number" && Number.isInteger(owner.processGroupId) ? owner.processGroupId : pid;
143
- const command = Array.isArray(owner.command) ? owner.command.filter((value): value is string => typeof value === "string") : [];
144
- return { token: owner.token, acquiredAt: owner.acquiredAt, heartbeatAt: owner.heartbeatAt, pid, processGroupId, command };
161
+ if (typeof owner.token !== "string" || owner.token.length === 0)
162
+ return undefined;
163
+ if (
164
+ typeof owner.acquiredAt !== "string" ||
165
+ Number.isNaN(Date.parse(owner.acquiredAt))
166
+ )
167
+ return undefined;
168
+ if (
169
+ typeof owner.heartbeatAt !== "string" ||
170
+ Number.isNaN(Date.parse(owner.heartbeatAt))
171
+ )
172
+ return undefined;
173
+ const pid =
174
+ typeof owner.pid === "number" && Number.isInteger(owner.pid)
175
+ ? owner.pid
176
+ : 0;
177
+ const processGroupId =
178
+ typeof owner.processGroupId === "number" &&
179
+ Number.isInteger(owner.processGroupId)
180
+ ? owner.processGroupId
181
+ : pid;
182
+ const command = Array.isArray(owner.command)
183
+ ? owner.command.filter(
184
+ (value): value is string => typeof value === "string",
185
+ )
186
+ : [];
187
+ return {
188
+ token: owner.token,
189
+ acquiredAt: owner.acquiredAt,
190
+ heartbeatAt: owner.heartbeatAt,
191
+ pid,
192
+ processGroupId,
193
+ command,
194
+ };
145
195
  }
146
196
 
147
197
  function isRunLeaseStale(owner: NornRunLeaseOwner): boolean {
@@ -23,37 +23,66 @@ export type NornRunManifest = {
23
23
  export class NornRunLogger {
24
24
  private isInitialized = false;
25
25
 
26
- constructor(private readonly input: {
27
- readonly manifestPath: string;
28
- readonly manifest: Omit<NornRunManifest, "events">;
29
- readonly files: NornFileCoordinator;
30
- }) {}
26
+ constructor(
27
+ private readonly input: {
28
+ readonly manifestPath: string;
29
+ readonly manifest: Omit<NornRunManifest, "events">;
30
+ readonly files: NornFileCoordinator;
31
+ },
32
+ ) {}
31
33
 
32
- static async load(manifestPath: string, files: NornFileCoordinator): Promise<NornRunLogger> {
34
+ static async load(
35
+ manifestPath: string,
36
+ files: NornFileCoordinator,
37
+ ): Promise<NornRunLogger> {
33
38
  const manifest = parseManifest(await files.readText(manifestPath));
34
39
  const logger = new NornRunLogger({ manifestPath, manifest, files });
35
40
  logger.isInitialized = true;
36
41
  return logger;
37
42
  }
38
43
 
39
- async record(event: { readonly type: string; readonly [key: string]: unknown }): Promise<void> {
40
- await this.input.files.withExclusiveLock(this.input.manifestPath, async (path) => {
41
- let manifest: NornRunManifest;
42
- try {
43
- manifest = parseManifest(await readFile(path, "utf8"));
44
- } catch (error) {
45
- if (this.isInitialized || !isNodeError(error) || error.code !== "ENOENT") throw error;
46
- manifest = { ...this.input.manifest, events: [] };
47
- }
48
- if (manifest.id !== this.input.manifest.id) throw new Error("Run manifest identity changed");
49
- await writeJsonAtomically(path, { ...manifest, events: [...manifest.events, { ...event, at: new Date().toISOString() }] });
50
- this.isInitialized = true;
51
- });
44
+ async record(event: {
45
+ readonly type: string;
46
+ readonly [key: string]: unknown;
47
+ }): Promise<void> {
48
+ await this.input.files.withExclusiveLock(
49
+ this.input.manifestPath,
50
+ async (path) => {
51
+ let manifest: NornRunManifest;
52
+ try {
53
+ manifest = parseManifest(await readFile(path, "utf8"));
54
+ } catch (error) {
55
+ if (
56
+ this.isInitialized ||
57
+ !isNodeError(error) ||
58
+ error.code !== "ENOENT"
59
+ )
60
+ throw error;
61
+ manifest = { ...this.input.manifest, events: [] };
62
+ }
63
+ if (manifest.id !== this.input.manifest.id)
64
+ throw new Error("Run manifest identity changed");
65
+ await writeJsonAtomically(path, {
66
+ ...manifest,
67
+ events: [
68
+ ...manifest.events,
69
+ { ...event, at: new Date().toISOString() },
70
+ ],
71
+ });
72
+ this.isInitialized = true;
73
+ },
74
+ );
52
75
  }
53
76
  }
54
77
 
55
78
  function parseManifest(content: string): NornRunManifest {
56
79
  const manifest = JSON.parse(content) as NornRunManifest;
57
- if (!manifest || typeof manifest !== "object" || typeof manifest.id !== "string" || !Array.isArray(manifest.events)) throw new Error("Invalid run manifest");
80
+ if (
81
+ !manifest ||
82
+ typeof manifest !== "object" ||
83
+ typeof manifest.id !== "string" ||
84
+ !Array.isArray(manifest.events)
85
+ )
86
+ throw new Error("Invalid run manifest");
58
87
  return manifest;
59
88
  }
@@ -1,22 +1,130 @@
1
1
  const ADJECTIVES = [
2
- "able", "amber", "brave", "bright", "calm", "clever", "crisp", "daring", "eager", "fair",
3
- "fast", "fresh", "gentle", "golden", "grand", "happy", "honest", "jolly", "kind", "lively",
4
- "lucky", "merry", "nimble", "noble", "patient", "peaceful", "proud", "quiet", "rapid", "ready",
5
- "royal", "sharp", "silent", "silver", "simple", "steady", "sunny", "swift", "tidy", "vivid",
2
+ "able",
3
+ "amber",
4
+ "brave",
5
+ "bright",
6
+ "calm",
7
+ "clever",
8
+ "crisp",
9
+ "daring",
10
+ "eager",
11
+ "fair",
12
+ "fast",
13
+ "fresh",
14
+ "gentle",
15
+ "golden",
16
+ "grand",
17
+ "happy",
18
+ "honest",
19
+ "jolly",
20
+ "kind",
21
+ "lively",
22
+ "lucky",
23
+ "merry",
24
+ "nimble",
25
+ "noble",
26
+ "patient",
27
+ "peaceful",
28
+ "proud",
29
+ "quiet",
30
+ "rapid",
31
+ "ready",
32
+ "royal",
33
+ "sharp",
34
+ "silent",
35
+ "silver",
36
+ "simple",
37
+ "steady",
38
+ "sunny",
39
+ "swift",
40
+ "tidy",
41
+ "vivid",
6
42
  ];
7
43
 
8
44
  const MODIFIERS = [
9
- "anchor", "autumn", "brook", "cedar", "cloud", "copper", "dawn", "ember", "field", "forest",
10
- "harbor", "hazel", "hill", "island", "ivory", "jade", "lake", "maple", "meadow", "moon",
11
- "north", "ocean", "olive", "pearl", "pine", "rain", "river", "rose", "shadow", "snow",
12
- "solar", "spring", "stone", "summer", "thunder", "timber", "valley", "violet", "willow", "winter",
45
+ "anchor",
46
+ "autumn",
47
+ "brook",
48
+ "cedar",
49
+ "cloud",
50
+ "copper",
51
+ "dawn",
52
+ "ember",
53
+ "field",
54
+ "forest",
55
+ "harbor",
56
+ "hazel",
57
+ "hill",
58
+ "island",
59
+ "ivory",
60
+ "jade",
61
+ "lake",
62
+ "maple",
63
+ "meadow",
64
+ "moon",
65
+ "north",
66
+ "ocean",
67
+ "olive",
68
+ "pearl",
69
+ "pine",
70
+ "rain",
71
+ "river",
72
+ "rose",
73
+ "shadow",
74
+ "snow",
75
+ "solar",
76
+ "spring",
77
+ "stone",
78
+ "summer",
79
+ "thunder",
80
+ "timber",
81
+ "valley",
82
+ "violet",
83
+ "willow",
84
+ "winter",
13
85
  ];
14
86
 
15
87
  const NOUNS = [
16
- "badger", "beacon", "bison", "canvas", "comet", "falcon", "fjord", "fox", "garden", "glade",
17
- "grove", "heron", "lantern", "lynx", "marker", "otter", "panda", "planet", "prairie", "raven",
18
- "rocket", "sail", "signal", "sparrow", "summit", "tiger", "trail", "voyage", "whale", "wolf",
19
- "zephyr", "orchid", "bridge", "castle", "harvest", "mirror", "needle", "quartz", "ripple", "signal",
88
+ "badger",
89
+ "beacon",
90
+ "bison",
91
+ "canvas",
92
+ "comet",
93
+ "falcon",
94
+ "fjord",
95
+ "fox",
96
+ "garden",
97
+ "glade",
98
+ "grove",
99
+ "heron",
100
+ "lantern",
101
+ "lynx",
102
+ "marker",
103
+ "otter",
104
+ "panda",
105
+ "planet",
106
+ "prairie",
107
+ "raven",
108
+ "rocket",
109
+ "sail",
110
+ "signal",
111
+ "sparrow",
112
+ "summit",
113
+ "tiger",
114
+ "trail",
115
+ "voyage",
116
+ "whale",
117
+ "wolf",
118
+ "zephyr",
119
+ "orchid",
120
+ "bridge",
121
+ "castle",
122
+ "harvest",
123
+ "mirror",
124
+ "needle",
125
+ "quartz",
126
+ "ripple",
127
+ "signal",
20
128
  ];
21
129
 
22
130
  export function generateRunName(existingNames: ReadonlySet<string>): string {