@tt-a1i/hive 2.1.9 → 2.1.10
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/CHANGELOG.md +17 -0
- package/dist/src/server/agent-stdin-dispatcher.js +1 -0
- package/dist/src/server/post-start-input-writer.js +35 -6
- package/dist/src/server/routes-scenarios.d.ts +12 -17
- package/dist/src/server/routes-scenarios.js +41 -17
- package/dist/src/server/scenario-presets.d.ts +7 -4
- package/dist/src/server/scenario-presets.js +16 -6
- package/dist/src/shared/random-worker-name.d.ts +9 -0
- package/dist/src/shared/random-worker-name.js +382 -0
- package/package.json +1 -1
- package/web/dist/assets/{AddWorkerDialog-QBN0m0eH.js → AddWorkerDialog-Dc0slXtx.js} +2 -2
- package/web/dist/assets/{AddWorkspaceFlow-BJy-W2XI.js → AddWorkspaceFlow-AyzSdx5w.js} +1 -1
- package/web/dist/assets/{FirstRunWizard-V-idMQB7.js → FirstRunWizard-DGB5zWhl.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-D4ldgvSM.js → MarketplaceDrawer-DAZ-crqq.js} +1 -1
- package/web/dist/assets/{TaskGraphDrawer-BSxRbr1h.js → TaskGraphDrawer-DYk-0uKD.js} +1 -1
- package/web/dist/assets/{WhatsNewDialog-C-hpR8y4.js → WhatsNewDialog-B5tu3MV2.js} +1 -1
- package/web/dist/assets/{WorkerModal-EuNxsCtr.js → WorkerModal-C73K7SUg.js} +1 -1
- package/web/dist/assets/{WorkflowsDrawer-CoT6kWcM.js → WorkflowsDrawer-mrSAPqAh.js} +1 -1
- package/web/dist/assets/{WorkspaceMemoryDrawer-BXx4Z0kx.js → WorkspaceMemoryDrawer-CCz3HB4F.js} +1 -1
- package/web/dist/assets/{WorkspaceTaskDrawer-DKRRttxQ.js → WorkspaceTaskDrawer-C7JzoRp8.js} +1 -1
- package/web/dist/assets/index-CVQc8PLJ.js +84 -0
- package/web/dist/assets/{search-KbSLfFB8.js → search-Bx1xfpzr.js} +1 -1
- package/web/dist/assets/{square-terminal-C89RIZhu.js → square-terminal-Dfr9QC1I.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/index-D43vHIy2.js +0 -84
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable user-facing changes will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 2.1.10 - 2026-06-23
|
|
6
|
+
|
|
7
|
+
Scenario teams and terminal delivery polish.
|
|
8
|
+
|
|
9
|
+
- Starts members created from one-click scenarios immediately, so the team is
|
|
10
|
+
ready for the Orchestrator to assign work instead of sitting stopped.
|
|
11
|
+
- Uses the shared member-name pool for scenario-created members, matching the
|
|
12
|
+
Add Member dialog and avoiding generated stem-only names.
|
|
13
|
+
- Improves OpenCode dispatch delivery after startup by recognizing its completed
|
|
14
|
+
turn footer as a ready signal, while waiting briefly for the terminal output
|
|
15
|
+
to settle before injecting the next dispatch.
|
|
16
|
+
- Adds an explicit accepted-dispatch status instruction to member tasks, making
|
|
17
|
+
it easier to see that a member received a dispatch before it starts working.
|
|
18
|
+
- Keeps optimistic terminal panels tied to real run lifecycle events and
|
|
19
|
+
preserves Codex cursor-position repaint frames, improving terminal display
|
|
20
|
+
during editing and fast start/stop flows.
|
|
21
|
+
|
|
5
22
|
## 2.1.9 - 2026-06-23
|
|
6
23
|
|
|
7
24
|
Lean Hive guidance and desktop terminal polish.
|
|
@@ -42,6 +42,7 @@ export const buildWorkerDispatchPayload = (fromAgentName, workerDescription, dis
|
|
|
42
42
|
`Your role: ${escapeHiveEnvelopeText(workerDescription)}`,
|
|
43
43
|
'',
|
|
44
44
|
'You must:',
|
|
45
|
+
`- Immediately acknowledge receipt by running \`team status "accepted dispatch ${dispatchId}"\` before doing the task`,
|
|
45
46
|
`- When the task is done, failed, blocked, or partially done, run \`team report "<result>" --dispatch ${dispatchId}\``,
|
|
46
47
|
'- Do not do unrelated work; report as soon as you are done',
|
|
47
48
|
'- Never stop to wait for terminal input — if you need a decision, report the question as blocked instead',
|
|
@@ -4,6 +4,7 @@ const READY_CHECK_INTERVAL_MS = 50;
|
|
|
4
4
|
const READY_TIMEOUT_MS = 3000;
|
|
5
5
|
const PROMPT_READY_HARD_TIMEOUT_MS = 30000;
|
|
6
6
|
const PROMPT_READY_TAIL_CHARS = 8000;
|
|
7
|
+
const OPENCODE_COMPLETION_READY_SETTLE_MS = 1000;
|
|
7
8
|
const MIN_SUBMIT_AFTER_PASTE_DELAY_MS = 600;
|
|
8
9
|
const MAX_SUBMIT_AFTER_PASTE_DELAY_MS = 1500;
|
|
9
10
|
const PASTE_CHARS_PER_DELAY_MS = 4;
|
|
@@ -75,7 +76,8 @@ const hasHermesPromptReady = (output) => {
|
|
|
75
76
|
const plain = getPlainTerminalTail(output);
|
|
76
77
|
return /\bWelcome to Hermes Agent![\s\S]*❯/u.test(plain);
|
|
77
78
|
};
|
|
78
|
-
const
|
|
79
|
+
const hasOpencodeVisiblePromptReady = (output) => /\bAsk anything\.\.\./u.test(getPlainTerminalTail(output));
|
|
80
|
+
const hasOpencodeCompletedTurnReady = (output) => /(?:^|\s)▣\s+Build\s+·/u.test(getLastNonEmptyTerminalLine(output));
|
|
79
81
|
const hasGrokPromptReady = (output) => /\b(?:Enter:send|Composer\s+\S+)/u.test(output);
|
|
80
82
|
const hasAgyPromptReady = (output) => /(?:^|\n)\s*>\s*\n\s*(?:[─-]{8,}|\?\s*for shortcuts)/u.test(getPlainTerminalOutput(output));
|
|
81
83
|
const getLastNonEmptyTerminalLine = (output) => {
|
|
@@ -110,15 +112,24 @@ export const hasFirstRunSetupPrompt = (output) => {
|
|
|
110
112
|
const recent = recentLines.join('\n');
|
|
111
113
|
return SETUP_PROMPT_PATTERNS.some((pattern) => pattern.test(recent));
|
|
112
114
|
};
|
|
113
|
-
|
|
115
|
+
const getInteractivePromptReadyState = (output, command = '') => {
|
|
114
116
|
const commandName = getCommandName(command);
|
|
115
|
-
|
|
117
|
+
if (hasBarePromptLine(output) ||
|
|
116
118
|
(commandName === 'agy' && hasAgyPromptReady(output)) ||
|
|
117
119
|
(commandName === 'grok' && hasGrokPromptReady(output)) ||
|
|
118
120
|
(commandName === 'hermes' && hasHermesPromptReady(output)) ||
|
|
119
|
-
((commandName === 'gemini' || commandName === 'qwen') && hasGeminiPromptReady(output))
|
|
120
|
-
|
|
121
|
+
((commandName === 'gemini' || commandName === 'qwen') && hasGeminiPromptReady(output))) {
|
|
122
|
+
return 'ready';
|
|
123
|
+
}
|
|
124
|
+
if (commandName === 'opencode') {
|
|
125
|
+
if (hasOpencodeVisiblePromptReady(output))
|
|
126
|
+
return 'ready';
|
|
127
|
+
if (hasOpencodeCompletedTurnReady(output))
|
|
128
|
+
return 'ready-after-settle';
|
|
129
|
+
}
|
|
130
|
+
return 'not-ready';
|
|
121
131
|
};
|
|
132
|
+
export const hasInteractivePromptReady = (output, command = '') => getInteractivePromptReadyState(output, command) !== 'not-ready';
|
|
122
133
|
export const hasBracketedPasteAcknowledgement = (output, baselineLength) => {
|
|
123
134
|
const outputAfterPaste = output.slice(baselineLength);
|
|
124
135
|
const pasteEndIndex = outputAfterPaste.lastIndexOf(BRACKETED_PASTE_END);
|
|
@@ -282,6 +293,24 @@ export const createPostStartInputWriter = (agentManager, command) => {
|
|
|
282
293
|
});
|
|
283
294
|
const startedAt = Date.now();
|
|
284
295
|
let isInitialAttempt = true;
|
|
296
|
+
let settleCandidate = null;
|
|
297
|
+
const isReadyToWrite = (output) => {
|
|
298
|
+
const promptReadyState = getInteractivePromptReadyState(output, command);
|
|
299
|
+
if (promptReadyState === 'ready') {
|
|
300
|
+
settleCandidate = null;
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (promptReadyState !== 'ready-after-settle') {
|
|
304
|
+
settleCandidate = null;
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
const tail = getPlainTerminalTail(output);
|
|
308
|
+
if (!settleCandidate || settleCandidate.tail !== tail) {
|
|
309
|
+
settleCandidate = { since: Date.now(), tail };
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
return Date.now() - settleCandidate.since >= OPENCODE_COMPLETION_READY_SETTLE_MS;
|
|
313
|
+
};
|
|
285
314
|
const tryWrite = () => {
|
|
286
315
|
let output;
|
|
287
316
|
try {
|
|
@@ -297,7 +326,7 @@ export const createPostStartInputWriter = (agentManager, command) => {
|
|
|
297
326
|
return;
|
|
298
327
|
}
|
|
299
328
|
const firstRunSetupPromptVisible = hasFirstRunSetupPrompt(output);
|
|
300
|
-
if ((!firstRunSetupPromptVisible &&
|
|
329
|
+
if ((!firstRunSetupPromptVisible && isReadyToWrite(output)) ||
|
|
301
330
|
(!firstRunSetupPromptVisible &&
|
|
302
331
|
canTimeoutBeforePromptReady(command) &&
|
|
303
332
|
Date.now() - startedAt >= READY_TIMEOUT_MS)) {
|
|
@@ -4,23 +4,18 @@ import type { RuntimeStore } from './runtime-store-contract.js';
|
|
|
4
4
|
import { type ScenarioPreset } from './scenario-presets.js';
|
|
5
5
|
import { type CliAvailabilityProbe } from './spawn-cli-resolver.js';
|
|
6
6
|
import { type UiLanguage } from './workspace-ui-language.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Materialize the scenario team and hand the goal to the orchestrator.
|
|
10
|
-
* Exported for direct testing (the route harness has no PTY, so the
|
|
11
|
-
* active-run gate above would mask this logic).
|
|
12
|
-
*
|
|
13
|
-
* Workers derive a FRESH launch config from the orchestrator's CLI brand via
|
|
14
|
-
* the same resolver `team spawn` uses — never a clone of the orchestrator's
|
|
15
|
-
* own config, which may carry session-resume arguments that would make every
|
|
16
|
-
* worker resume the orchestrator's session.
|
|
17
|
-
*
|
|
18
|
-
* Worker creation is sequential and NOT transactional across workers: each
|
|
19
|
-
* addWorkerWithLaunch is atomic, but a mid-loop failure (e.g. a name race)
|
|
20
|
-
* leaves earlier workers in place and surfaces the error to the caller.
|
|
21
|
-
*/
|
|
22
|
-
export declare const applyScenario: (store: RuntimeStore, workspaceId: string, scenario: ScenarioPreset, goal: string, isCommandAvailable?: CliAvailabilityProbe, language?: UiLanguage) => Array<{
|
|
7
|
+
type CreatedScenarioWorker = {
|
|
23
8
|
id: string;
|
|
24
9
|
name: string;
|
|
25
10
|
role: WorkerRole;
|
|
26
|
-
}
|
|
11
|
+
};
|
|
12
|
+
type StartedScenarioWorker = CreatedScenarioWorker & {
|
|
13
|
+
start: {
|
|
14
|
+
ok: true;
|
|
15
|
+
run_id: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare const scenarioRoutes: RouteDefinition[];
|
|
19
|
+
export declare const applyScenario: (store: RuntimeStore, workspaceId: string, scenario: ScenarioPreset, goal: string, isCommandAvailable?: CliAvailabilityProbe, language?: UiLanguage) => CreatedScenarioWorker[];
|
|
20
|
+
export declare const applyAndStartScenario: (store: RuntimeStore, workspaceId: string, scenario: ScenarioPreset, goal: string, hivePort: string, isCommandAvailable?: CliAvailabilityProbe, language?: UiLanguage) => Promise<StartedScenarioWorker[]>;
|
|
21
|
+
export {};
|
|
@@ -5,12 +5,13 @@ import { resolveDefaultSpawnCliLaunchConfig, } from './spawn-cli-resolver.js';
|
|
|
5
5
|
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
6
6
|
import { getOrchestratorId } from './workspace-store-support.js';
|
|
7
7
|
import { resolveWorkspaceUiLanguage, writeWorkspaceUiLanguage, } from './workspace-ui-language.js';
|
|
8
|
+
const getRuntimePort = (request) => String(request.socket.localPort ?? '');
|
|
8
9
|
export const scenarioRoutes = [
|
|
9
10
|
/**
|
|
10
|
-
* One-click team assembly: materialize a scenario preset's
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* One-click team assembly: materialize a scenario preset's members, start
|
|
12
|
+
* their PTYs, then hand the user's goal to the orchestrator as a normal user
|
|
13
|
+
* message. Deliberately NOT dispatching — splitting the goal and calling
|
|
14
|
+
* `team send` stays the orchestrator's job.
|
|
14
15
|
*/
|
|
15
16
|
route('POST', '/api/workspaces/:workspaceId/scenarios/:scenarioId/apply', async ({ params, request, response, store }) => {
|
|
16
17
|
const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id and scenario id are required');
|
|
@@ -35,13 +36,12 @@ export const scenarioRoutes = [
|
|
|
35
36
|
}
|
|
36
37
|
const language = resolveWorkspaceUiLanguage(store.settings, workspaceId, body.locale);
|
|
37
38
|
writeWorkspaceUiLanguage(store.settings, workspaceId, language);
|
|
38
|
-
// The kickoff
|
|
39
|
-
//
|
|
40
|
-
// goal. Gate up front: no live run, no team assembly.
|
|
39
|
+
// The kickoff must land in the orchestrator's live terminal. Gate up
|
|
40
|
+
// front so a stopped orchestrator never gets a half-created scenario.
|
|
41
41
|
if (!store.getActiveRunByAgentId(workspaceId, getOrchestratorId(workspaceId))) {
|
|
42
42
|
throw new ConflictError('Start the Orchestrator first — the scenario goal is handed to its terminal');
|
|
43
43
|
}
|
|
44
|
-
const created =
|
|
44
|
+
const created = await applyAndStartScenario(store, workspaceId, scenario, goal, getRuntimePort(request), undefined, language);
|
|
45
45
|
sendJson(response, 201, {
|
|
46
46
|
created_workers: created,
|
|
47
47
|
injected: true,
|
|
@@ -51,7 +51,7 @@ export const scenarioRoutes = [
|
|
|
51
51
|
/**
|
|
52
52
|
* Materialize the scenario team and hand the goal to the orchestrator.
|
|
53
53
|
* Exported for direct testing (the route harness has no PTY, so the
|
|
54
|
-
* active-run
|
|
54
|
+
* active-run/start behavior would mask this logic).
|
|
55
55
|
*
|
|
56
56
|
* Workers derive a FRESH launch config from the orchestrator's CLI brand via
|
|
57
57
|
* the same resolver `team spawn` uses — never a clone of the orchestrator's
|
|
@@ -62,7 +62,7 @@ export const scenarioRoutes = [
|
|
|
62
62
|
* addWorkerWithLaunch is atomic, but a mid-loop failure (e.g. a name race)
|
|
63
63
|
* leaves earlier workers in place and surfaces the error to the caller.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
const createScenarioWorkers = (store, workspaceId, scenario, isCommandAvailable, language = 'en') => {
|
|
66
66
|
const ports = {
|
|
67
67
|
getCommandPreset: (id) => store.settings.getCommandPreset(id),
|
|
68
68
|
getOrchestratorLaunchConfig: () => store.peekAgentLaunchConfig(workspaceId, getOrchestratorId(workspaceId)),
|
|
@@ -72,7 +72,7 @@ export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvail
|
|
|
72
72
|
const takenNames = new Set(store.listWorkers(workspaceId).map((worker) => worker.name));
|
|
73
73
|
const created = [];
|
|
74
74
|
for (const spec of scenario.workers) {
|
|
75
|
-
const name = buildScenarioWorkerName(spec,
|
|
75
|
+
const name = buildScenarioWorkerName(spec, takenNames, { language });
|
|
76
76
|
takenNames.add(name);
|
|
77
77
|
const description = spec.descriptionOverride !== undefined
|
|
78
78
|
? getScenarioWorkerDescription(spec, language)
|
|
@@ -84,11 +84,35 @@ export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvail
|
|
|
84
84
|
}, launchConfig);
|
|
85
85
|
created.push({ id: worker.id, name: worker.name, role: spec.role });
|
|
86
86
|
}
|
|
87
|
-
store.recordUserInput(workspaceId, getOrchestratorId(workspaceId), buildScenarioKickoffMessage({
|
|
88
|
-
scenarioId: scenario.id,
|
|
89
|
-
goal,
|
|
90
|
-
language,
|
|
91
|
-
workers: created.map((worker) => ({ name: worker.name, role: worker.role })),
|
|
92
|
-
}));
|
|
93
87
|
return created;
|
|
94
88
|
};
|
|
89
|
+
const deliverScenarioKickoff = async (store, workspaceId, scenario, goal, language, created) => {
|
|
90
|
+
await store.deliverUserInput(workspaceId, getOrchestratorId(workspaceId), buildScenarioKickoffText(scenario, goal, language, created));
|
|
91
|
+
};
|
|
92
|
+
const buildScenarioKickoffText = (scenario, goal, language, created) => buildScenarioKickoffMessage({
|
|
93
|
+
scenarioId: scenario.id,
|
|
94
|
+
goal,
|
|
95
|
+
language,
|
|
96
|
+
workers: created.map((worker) => ({ name: worker.name, role: worker.role })),
|
|
97
|
+
});
|
|
98
|
+
const recordScenarioKickoff = (store, workspaceId, scenario, goal, language, created) => {
|
|
99
|
+
store.recordUserInput(workspaceId, getOrchestratorId(workspaceId), buildScenarioKickoffText(scenario, goal, language, created));
|
|
100
|
+
};
|
|
101
|
+
export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvailable, language = 'en') => {
|
|
102
|
+
const created = createScenarioWorkers(store, workspaceId, scenario, isCommandAvailable, language);
|
|
103
|
+
recordScenarioKickoff(store, workspaceId, scenario, goal, language, created);
|
|
104
|
+
return created;
|
|
105
|
+
};
|
|
106
|
+
const startScenarioWorkers = async (store, workspaceId, hivePort, created) => Promise.all(created.map(async (worker) => {
|
|
107
|
+
const run = await store.startAgent(workspaceId, worker.id, { hivePort });
|
|
108
|
+
if (run.status === 'error') {
|
|
109
|
+
throw new ConflictError(`Failed to start scenario member: ${worker.name}`);
|
|
110
|
+
}
|
|
111
|
+
return { ...worker, start: { ok: true, run_id: run.runId } };
|
|
112
|
+
}));
|
|
113
|
+
export const applyAndStartScenario = async (store, workspaceId, scenario, goal, hivePort, isCommandAvailable, language = 'en') => {
|
|
114
|
+
const created = createScenarioWorkers(store, workspaceId, scenario, isCommandAvailable, language);
|
|
115
|
+
const started = await startScenarioWorkers(store, workspaceId, hivePort, created);
|
|
116
|
+
await deliverScenarioKickoff(store, workspaceId, scenario, goal, language, created);
|
|
117
|
+
return started;
|
|
118
|
+
};
|
|
@@ -3,11 +3,14 @@ import type { WorkerRole } from '../shared/types.js';
|
|
|
3
3
|
import type { UiLanguage } from '../shared/ui-language.js';
|
|
4
4
|
export { getScenarioPreset, getScenarioWorkerDescription, SCENARIO_PRESETS, type ScenarioId, type ScenarioPreset, type ScenarioWorkerSpec, } from '../shared/scenario-presets.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Scenario member names come from the same role-flavored historical-figure
|
|
7
|
+
* pool the Add Member dialog uses. When a pool is exhausted in a busy
|
|
8
|
+
* workspace, append a short suffix instead of failing the whole scenario.
|
|
9
9
|
*/
|
|
10
|
-
export declare const buildScenarioWorkerName: (spec: Pick<ScenarioWorkerSpec, "nameStem">,
|
|
10
|
+
export declare const buildScenarioWorkerName: (spec: Pick<ScenarioWorkerSpec, "nameStem" | "role">, usedNames: ReadonlySet<string>, input?: {
|
|
11
|
+
language?: UiLanguage;
|
|
12
|
+
nextUint32?: () => number;
|
|
13
|
+
}, maxAttempts?: number) => string;
|
|
11
14
|
export interface ScenarioKickoffInput {
|
|
12
15
|
scenarioId: string;
|
|
13
16
|
goal: string;
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { generateWorkerName } from '../shared/random-worker-name.js';
|
|
2
3
|
export { getScenarioPreset, getScenarioWorkerDescription, SCENARIO_PRESETS, } from '../shared/scenario-presets.js';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Scenario member names come from the same role-flavored historical-figure
|
|
6
|
+
* pool the Add Member dialog uses. When a pool is exhausted in a busy
|
|
7
|
+
* workspace, append a short suffix instead of failing the whole scenario.
|
|
7
8
|
*/
|
|
8
|
-
export const buildScenarioWorkerName = (spec,
|
|
9
|
+
export const buildScenarioWorkerName = (spec, usedNames, input = {}, maxAttempts = 16) => {
|
|
10
|
+
const candidate = generateWorkerName({
|
|
11
|
+
role: spec.role,
|
|
12
|
+
usedNames,
|
|
13
|
+
...(input.language !== undefined ? { language: input.language } : {}),
|
|
14
|
+
...(input.nextUint32 !== undefined ? { nextUint32: input.nextUint32 } : {}),
|
|
15
|
+
});
|
|
16
|
+
if (!usedNames.has(candidate))
|
|
17
|
+
return candidate;
|
|
18
|
+
const base = candidate || spec.nameStem;
|
|
9
19
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
10
20
|
const suffix = randomBytes(3).readUIntBE(0, 3).toString(36).padStart(4, '0').slice(-4);
|
|
11
|
-
const name = `${
|
|
12
|
-
if (!
|
|
21
|
+
const name = `${base}-${suffix}`;
|
|
22
|
+
if (!usedNames.has(name))
|
|
13
23
|
return name;
|
|
14
24
|
}
|
|
15
25
|
throw new Error(`Could not generate a unique member name for: ${spec.nameStem}`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WorkerRole } from './types.js';
|
|
2
|
+
import type { UiLanguage } from './ui-language.js';
|
|
3
|
+
export interface GenerateWorkerNameOptions {
|
|
4
|
+
language?: UiLanguage;
|
|
5
|
+
role?: WorkerRole;
|
|
6
|
+
usedNames?: ReadonlySet<string>;
|
|
7
|
+
nextUint32?: () => number;
|
|
8
|
+
}
|
|
9
|
+
export declare const generateWorkerName: ({ language, role, usedNames, nextUint32, }?: GenerateWorkerNameOptions) => string;
|