vibe-coding-master 0.0.5 → 0.0.7
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/README.md +168 -63
- package/dist/backend/adapters/translation-provider.js +145 -0
- package/dist/backend/api/artifact-routes.js +3 -0
- package/dist/backend/api/harness-routes.js +22 -0
- package/dist/backend/api/project-routes.js +3 -8
- package/dist/backend/api/translation-routes.js +70 -0
- package/dist/backend/runtime/node-pty-runtime.js +20 -18
- package/dist/backend/server.js +31 -1
- package/dist/backend/services/app-settings-service.js +128 -0
- package/dist/backend/services/artifact-service.js +7 -4
- package/dist/backend/services/claude-transcript-service.js +509 -0
- package/dist/backend/services/harness-service.js +178 -0
- package/dist/backend/services/project-service.js +4 -0
- package/dist/backend/services/session-service.js +7 -5
- package/dist/backend/services/status-service.js +76 -0
- package/dist/backend/services/translation-prompts.js +173 -0
- package/dist/backend/services/translation-queue.js +39 -0
- package/dist/backend/services/translation-service.js +546 -0
- package/dist/backend/templates/handoff.js +32 -0
- package/dist/backend/templates/harness/architect-agent.js +12 -0
- package/dist/backend/templates/harness/claude-root.js +14 -0
- package/dist/backend/templates/harness/coder-agent.js +11 -0
- package/dist/backend/templates/harness/project-manager-agent.js +14 -0
- package/dist/backend/templates/harness/reviewer-agent.js +13 -0
- package/dist/backend/ws/translation-ws.js +35 -0
- package/dist/shared/types/harness.js +1 -0
- package/dist/shared/types/translation.js +5 -0
- package/dist/shared/validation/artifact-check.js +15 -1
- package/dist/shared/validation/language-detect.js +46 -0
- package/dist-frontend/assets/index-BNASqKEK.css +32 -0
- package/dist-frontend/assets/index-Bp49_End.js +58 -0
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +93 -36
- package/docs/product-design.md +313 -1408
- package/docs/v1-architecture-design.md +500 -1153
- package/docs/v1-implementation-plan.md +783 -1604
- package/package.json +3 -1
- package/scripts/verify-package.mjs +121 -0
- package/dist/backend/templates/role-messaging-context.js +0 -44
- package/dist-frontend/assets/index-Bah6k-Ix.css +0 -32
- package/dist-frontend/assets/index-EMaQuIB6.js +0 -58
- package/docs/v1-message-bus-orchestration-design.md +0 -534
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
5
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
6
|
-
import {
|
|
6
|
+
import { claudeTranscriptPath } from "./claude-transcript-service.js";
|
|
7
7
|
export function createSessionService(deps) {
|
|
8
8
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
9
9
|
async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
|
|
@@ -27,6 +27,9 @@ export function createSessionService(deps) {
|
|
|
27
27
|
hint: "Start the role once before using Resume."
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
+
const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
|
|
31
|
+
? persisted.transcriptPath
|
|
32
|
+
: claudeTranscriptPath(repoRoot, claudeSessionId);
|
|
30
33
|
const startCommand = deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume");
|
|
31
34
|
const runtimeSession = await deps.runtime.createSession({
|
|
32
35
|
taskSlug,
|
|
@@ -48,6 +51,7 @@ export function createSessionService(deps) {
|
|
|
48
51
|
const record = {
|
|
49
52
|
id: runtimeSession.id,
|
|
50
53
|
claudeSessionId,
|
|
54
|
+
transcriptPath,
|
|
51
55
|
taskSlug,
|
|
52
56
|
role,
|
|
53
57
|
status: runtimeSession.status,
|
|
@@ -69,7 +73,6 @@ export function createSessionService(deps) {
|
|
|
69
73
|
deps.registry.upsert(record);
|
|
70
74
|
await persistTaskSession(deps.fs, repoRoot, config.stateRoot, record);
|
|
71
75
|
await deps.taskService.updateTaskStatus(repoRoot, taskSlug, "running");
|
|
72
|
-
deps.runtime.write(runtimeSession.id, `${renderRoleMessagingContext(task, paths, role, deps.vcmctlCommand)}\r`);
|
|
73
76
|
return record;
|
|
74
77
|
}
|
|
75
78
|
return {
|
|
@@ -110,9 +113,7 @@ export function createSessionService(deps) {
|
|
|
110
113
|
await deps.runtime.stop(existing.id);
|
|
111
114
|
}
|
|
112
115
|
deps.registry.remove(existing.id);
|
|
113
|
-
return
|
|
114
|
-
? launchRoleSession(repoRoot, taskSlug, role, input, "resume")
|
|
115
|
-
: launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
|
|
116
|
+
return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
|
|
116
117
|
},
|
|
117
118
|
async getRoleSession(repoRoot, taskSlug, role) {
|
|
118
119
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
@@ -197,6 +198,7 @@ async function persistTaskSession(fs, repoRoot, stateRoot, session) {
|
|
|
197
198
|
[session.role]: {
|
|
198
199
|
id: session.id,
|
|
199
200
|
claudeSessionId: session.claudeSessionId,
|
|
201
|
+
transcriptPath: session.transcriptPath,
|
|
200
202
|
status: session.status,
|
|
201
203
|
record
|
|
202
204
|
}
|
|
@@ -14,8 +14,84 @@ export function createStatusService(deps) {
|
|
|
14
14
|
task,
|
|
15
15
|
sessions,
|
|
16
16
|
artifacts,
|
|
17
|
+
workflow: buildWorkflowReport(artifacts, sessions),
|
|
17
18
|
warnings
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
}
|
|
23
|
+
function buildWorkflowReport(artifacts, sessions) {
|
|
24
|
+
const isComplete = (kind) => artifacts.checks.find((check) => check.kind === kind)?.status === "ok";
|
|
25
|
+
const roleIsRunning = (role) => sessions.some((session) => session.role === role && session.status === "running");
|
|
26
|
+
const architectureComplete = isComplete("architecture-plan");
|
|
27
|
+
const implementationComplete = isComplete("implementation-log") && isComplete("validation-log");
|
|
28
|
+
const reviewComplete = isComplete("review-report");
|
|
29
|
+
const docsSyncComplete = isComplete("docs-sync-report");
|
|
30
|
+
const steps = [
|
|
31
|
+
{
|
|
32
|
+
id: "architecture-plan",
|
|
33
|
+
label: "Architecture",
|
|
34
|
+
role: "architect",
|
|
35
|
+
artifactPaths: [artifacts.paths.architecturePlanPath],
|
|
36
|
+
status: architectureComplete ? "complete" : "ready",
|
|
37
|
+
detail: architectureComplete
|
|
38
|
+
? "architecture-plan.md is ready."
|
|
39
|
+
: roleIsRunning("architect")
|
|
40
|
+
? "Architect is running; produce architecture-plan.md before coder work."
|
|
41
|
+
: "Start architect and produce architecture-plan.md before coder work."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "implementation",
|
|
45
|
+
label: "Implementation",
|
|
46
|
+
role: "coder",
|
|
47
|
+
artifactPaths: [artifacts.paths.implementationLogPath, artifacts.paths.validationLogPath],
|
|
48
|
+
status: implementationComplete ? "complete" : architectureComplete ? "ready" : "blocked",
|
|
49
|
+
detail: implementationComplete
|
|
50
|
+
? "implementation-log.md and validation-log.md are ready."
|
|
51
|
+
: architectureComplete
|
|
52
|
+
? "Start coder, then update implementation-log.md and validation-log.md."
|
|
53
|
+
: "Blocked until architecture-plan.md is complete."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "review",
|
|
57
|
+
label: "Review",
|
|
58
|
+
role: "reviewer",
|
|
59
|
+
artifactPaths: [artifacts.paths.reviewReportPath],
|
|
60
|
+
status: reviewComplete ? "complete" : implementationComplete ? "ready" : "blocked",
|
|
61
|
+
detail: reviewComplete
|
|
62
|
+
? "review-report.md is ready."
|
|
63
|
+
: implementationComplete
|
|
64
|
+
? "Start reviewer for independent review and final test adequacy."
|
|
65
|
+
: "Blocked until implementation-log.md and validation-log.md are complete."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "docs-sync",
|
|
69
|
+
label: "Docs Sync",
|
|
70
|
+
role: "architect",
|
|
71
|
+
artifactPaths: [artifacts.paths.docsSyncReportPath],
|
|
72
|
+
status: docsSyncComplete ? "complete" : reviewComplete ? "ready" : "blocked",
|
|
73
|
+
detail: docsSyncComplete
|
|
74
|
+
? "docs-sync-report.md is ready."
|
|
75
|
+
: reviewComplete
|
|
76
|
+
? "Send architect a docs-sync / architecture drift check task."
|
|
77
|
+
: "Blocked until review-report.md is complete."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "final-acceptance",
|
|
81
|
+
label: "PM Final",
|
|
82
|
+
role: "project-manager",
|
|
83
|
+
artifactPaths: [],
|
|
84
|
+
status: docsSyncComplete ? "ready" : "blocked",
|
|
85
|
+
detail: docsSyncComplete
|
|
86
|
+
? "Project Manager can prepare final acceptance, commit, and PR."
|
|
87
|
+
: "Blocked until architect docs sync is complete."
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
const current = steps.find((step) => step.status !== "complete") ?? steps[steps.length - 1];
|
|
91
|
+
return {
|
|
92
|
+
currentStepId: current.id,
|
|
93
|
+
nextAction: current.detail,
|
|
94
|
+
blocked: current.status === "blocked",
|
|
95
|
+
steps
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { TRANSLATION_PROMPT_KEYS } from "../../shared/types/translation.js";
|
|
2
|
+
const ZH_TO_EN_BASE = `You are a professional translator. The user types Chinese instructions for Claude Code (an AI coding assistant CLI), and your job is to translate the Chinese into natural, professional technical English that Claude Code will read as the user's prompt.
|
|
3
|
+
|
|
4
|
+
Output ONLY the translation. No preface, no quotation marks, no commentary, no notes about the translation.
|
|
5
|
+
|
|
6
|
+
Preserve verbatim (NEVER translate):
|
|
7
|
+
- code blocks (fenced or indented), code fragments, anything in backticks
|
|
8
|
+
- identifier names (variables, functions, types, classes, files, modules, packages, branches, tags)
|
|
9
|
+
- file paths, glob patterns, URLs
|
|
10
|
+
- command-line snippets, flags, environment variable names, git refs
|
|
11
|
+
- error messages quoted in the source
|
|
12
|
+
- numbers, units, version strings, hex / hash values
|
|
13
|
+
|
|
14
|
+
Translate only the prose around these.
|
|
15
|
+
|
|
16
|
+
Style:
|
|
17
|
+
- Concise. Match the source's length; do not pad with explanations the source does not contain.
|
|
18
|
+
- Imperative voice when the source is imperative ("帮我看一下" → "Take a look at"); declarative otherwise.
|
|
19
|
+
- Silently fix obvious typos, missing words, and ungrammatical fragments. Common patterns from a Chinese IME:
|
|
20
|
+
· 同音/近音字混用: "在" ↔ "再", "的/地/得", "做/作", "象/像", "因为/应为"
|
|
21
|
+
· 错别字: "测式" → "测试", "克立" → "克隆", "提价" → "提交"
|
|
22
|
+
· 漏字 / 多字: "一条录" → "一条记录"
|
|
23
|
+
· 缺主语或谓语: 句子片段 → 完整祈使句
|
|
24
|
+
ONLY fix when the intent is unambiguous. If a word could legitimately mean either reading (e.g. "在" really meant 在 not 再), translate AS-IS rather than guess. Conservative repair beats confident wrong-fix.
|
|
25
|
+
- If the input is brief or fragmentary, produce a complete grammatical English sentence reflecting the user's likely intent — but do not invent specifics the user did not imply.
|
|
26
|
+
- Aim for the register of a competent engineer writing a message to a colleague: clear, direct, no fluff, no hedging.`;
|
|
27
|
+
const ZH_TO_EN_WITH_CONTEXT_BASE = `You are a professional translator AND a quick sanity-check filter. The user types Chinese instructions for Claude Code (an AI coding assistant CLI). The PRIOR_REPLY block below is Claude Code's previous English reply — use it to disambiguate pronouns ("那个" / "你说的"), expand elliptical references ("再加一条" → "add one more entry"), silently correct obvious typos, AND detect when the user's input is unlikely to be actionable in this context.
|
|
28
|
+
|
|
29
|
+
Output format (MANDATORY — two parts separated by ONE blank line):
|
|
30
|
+
|
|
31
|
+
Part 1 — first line, status:
|
|
32
|
+
"OK" — normal: the user's input is clear given the prior reply
|
|
33
|
+
"WARN: <说明>" — flag a likely problem; ONE Chinese sentence (≤30 字)
|
|
34
|
+
|
|
35
|
+
(single blank line)
|
|
36
|
+
|
|
37
|
+
Part 2 — the English translation of the NEW USER INPUT, exactly as
|
|
38
|
+
you would translate it. Even when WARN is set, still translate as
|
|
39
|
+
faithfully as possible — the user may decide to send it anyway.
|
|
40
|
+
|
|
41
|
+
WARN ONLY when confident a Chinese-speaking engineer reading the
|
|
42
|
+
prior reply + the user's input would also see a problem. Triggers:
|
|
43
|
+
- The user refers to something not in PRIOR_REPLY ("那个文件" but
|
|
44
|
+
no file mentioned; "刚才说的方案" but Claude proposed nothing)
|
|
45
|
+
- The user answers ambiguously to a clear multiple-choice question
|
|
46
|
+
("好的" / "随便" when Claude asked "A or B?")
|
|
47
|
+
- The user's input contradicts the topic of PRIOR_REPLY (different
|
|
48
|
+
subject, mismatched verb)
|
|
49
|
+
- The input is garbled enough that even with context the
|
|
50
|
+
translation is just a guess
|
|
51
|
+
|
|
52
|
+
Output OK when in doubt. Spurious warnings slow the user down for
|
|
53
|
+
nothing; missed warnings just route through Claude Code, which can
|
|
54
|
+
ask for clarification itself. Do NOT WARN merely because the input
|
|
55
|
+
is short — short imperatives ("继续", "好") after a clear prior
|
|
56
|
+
reply are normal.
|
|
57
|
+
|
|
58
|
+
Translate only the prose; preserve verbatim:
|
|
59
|
+
- code blocks (fenced or indented), code fragments, anything in backticks
|
|
60
|
+
- identifier names, file paths, glob patterns, URLs
|
|
61
|
+
- command-line snippets, flags, environment variable names, git refs
|
|
62
|
+
- error messages quoted in the source
|
|
63
|
+
- numbers, units, version strings, hex / hash values
|
|
64
|
+
|
|
65
|
+
Style:
|
|
66
|
+
- Concise. Match the new input's length; do not pad with explanations.
|
|
67
|
+
- Imperative voice when the source is imperative; declarative otherwise.
|
|
68
|
+
- Silently fix obvious typos, missing words, and ungrammatical fragments. Common patterns from a Chinese IME:
|
|
69
|
+
· 同音/近音字混用: "在" ↔ "再", "的/地/得", "做/作", "象/像"
|
|
70
|
+
· 错别字: "测式" → "测试", "克立" → "克隆", "提价" → "提交"
|
|
71
|
+
· 漏字 / 多字: "一条录" → "一条记录"
|
|
72
|
+
The PRIOR_REPLY block is a strong disambiguation signal — if Claude just asked "Should I commit and push?" and the user types "提价 一下", "提价" is almost certainly "提交" given the context. ONLY fix when the intent is unambiguous; if uncertain, translate AS-IS.
|
|
73
|
+
- If the input is fragmentary, produce a complete grammatical English sentence reflecting the user's likely intent — using the prior reply as the disambiguation source, not as content to add.
|
|
74
|
+
- Aim for the register of a competent engineer writing a message to a colleague.`;
|
|
75
|
+
const EN_TO_ZH_BASE = `You are a professional translator. Claude Code (an AI coding assistant CLI) replies in English to a Chinese-speaking developer; your job is to render the English faithfully into natural, professional Simplified Chinese.
|
|
76
|
+
|
|
77
|
+
Output ONLY the translation. No preface, no quotation marks, no commentary.
|
|
78
|
+
|
|
79
|
+
Preserve verbatim (NEVER translate):
|
|
80
|
+
- code blocks (fenced or indented), code fragments, anything in backticks
|
|
81
|
+
- identifier names, file paths, glob patterns, URLs
|
|
82
|
+
- command-line snippets, flags, environment variable names, git refs
|
|
83
|
+
- error messages and stack traces inside code fences
|
|
84
|
+
- markdown structure (headings #, lists -/*/+, tables |, links, images, blockquotes >)
|
|
85
|
+
- numbers, units, version strings, hex / hash values
|
|
86
|
+
|
|
87
|
+
Translate only the prose around these.
|
|
88
|
+
|
|
89
|
+
Style:
|
|
90
|
+
- Concise. Match the source's length and structure.
|
|
91
|
+
- Natural Chinese technical writing conventions:
|
|
92
|
+
· 中英混排时,英文术语前后留半角空格(如 "调用 \`fetch\` 时")。
|
|
93
|
+
· 中文之间用全角标点(。,?!;:"" '')。
|
|
94
|
+
· 半角符号包裹的内容(括号 / 引号里全是英文)保持半角。
|
|
95
|
+
- Do not invent technical detail. If the English is ambiguous, leave the Chinese ambiguous; do not over-specify.
|
|
96
|
+
- The reader is a software engineer; sound like a Chinese-speaking engineer writing for them.`;
|
|
97
|
+
const PROMPT_BASES = {
|
|
98
|
+
"zh-to-en": ZH_TO_EN_BASE,
|
|
99
|
+
"zh-to-en-with-context": ZH_TO_EN_WITH_CONTEXT_BASE,
|
|
100
|
+
"en-to-zh": EN_TO_ZH_BASE
|
|
101
|
+
};
|
|
102
|
+
const PROMPT_LABELS = {
|
|
103
|
+
"zh-to-en": "zh-to-en",
|
|
104
|
+
"zh-to-en-with-context": "zh-to-en-with-context",
|
|
105
|
+
"en-to-zh": "en-to-zh"
|
|
106
|
+
};
|
|
107
|
+
export function buildTranslationPrompt(input) {
|
|
108
|
+
const key = getTranslationPromptKey(input);
|
|
109
|
+
if (input.direction === "user-input-to-english") {
|
|
110
|
+
if (key === "zh-to-en-with-context") {
|
|
111
|
+
return {
|
|
112
|
+
systemPrompt: resolveTranslationSystemPrompt(key, input.settings),
|
|
113
|
+
userPrompt: `[PRIOR CLAUDE CODE REPLY]\n${input.contextText}\n\n[NEW USER INPUT - translate only this]\n${input.text}`,
|
|
114
|
+
parseWarning: true
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
systemPrompt: resolveTranslationSystemPrompt(key, input.settings),
|
|
119
|
+
userPrompt: input.text,
|
|
120
|
+
parseWarning: false
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
systemPrompt: resolveTranslationSystemPrompt(key, input.settings),
|
|
125
|
+
userPrompt: input.text,
|
|
126
|
+
parseWarning: false
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function getTranslationPromptKey(input) {
|
|
130
|
+
if (input.direction === "user-input-to-english") {
|
|
131
|
+
return input.contextText?.trim()
|
|
132
|
+
? "zh-to-en-with-context"
|
|
133
|
+
: "zh-to-en";
|
|
134
|
+
}
|
|
135
|
+
return "en-to-zh";
|
|
136
|
+
}
|
|
137
|
+
export function getBaseTranslationPrompt(key, _settings) {
|
|
138
|
+
return PROMPT_BASES[key];
|
|
139
|
+
}
|
|
140
|
+
export function resolveTranslationSystemPrompt(key, settings) {
|
|
141
|
+
const override = settings.prompts?.[key];
|
|
142
|
+
return override?.trim() ? override : getBaseTranslationPrompt(key, settings);
|
|
143
|
+
}
|
|
144
|
+
export function getTranslationPromptPreviews(settings) {
|
|
145
|
+
return TRANSLATION_PROMPT_KEYS.map((key) => {
|
|
146
|
+
const defaultPrompt = getBaseTranslationPrompt(key, settings);
|
|
147
|
+
const userPrompt = settings.prompts?.[key]?.trim() ? settings.prompts[key] ?? "" : "";
|
|
148
|
+
return {
|
|
149
|
+
key,
|
|
150
|
+
label: PROMPT_LABELS[key],
|
|
151
|
+
defaultPrompt,
|
|
152
|
+
userPrompt,
|
|
153
|
+
customized: Boolean(userPrompt)
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export function parseTranslationWarning(raw) {
|
|
158
|
+
const trimmed = raw.trim();
|
|
159
|
+
const firstNewline = trimmed.indexOf("\n");
|
|
160
|
+
if (firstNewline === -1) {
|
|
161
|
+
return { text: trimmed };
|
|
162
|
+
}
|
|
163
|
+
const firstLine = trimmed.slice(0, firstNewline).trim();
|
|
164
|
+
const rest = trimmed.slice(firstNewline + 1).trim();
|
|
165
|
+
if (firstLine === "OK") {
|
|
166
|
+
return { text: rest };
|
|
167
|
+
}
|
|
168
|
+
if (firstLine.startsWith("WARN:")) {
|
|
169
|
+
const warning = firstLine.slice("WARN:".length).trim();
|
|
170
|
+
return warning ? { warning, text: rest } : { text: rest };
|
|
171
|
+
}
|
|
172
|
+
return { text: trimmed };
|
|
173
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function createSerialTranslationQueue() {
|
|
2
|
+
let chain = Promise.resolve();
|
|
3
|
+
let pendingCount = 0;
|
|
4
|
+
return {
|
|
5
|
+
enqueue(task) {
|
|
6
|
+
pendingCount += 1;
|
|
7
|
+
const run = async () => {
|
|
8
|
+
try {
|
|
9
|
+
return await task();
|
|
10
|
+
}
|
|
11
|
+
finally {
|
|
12
|
+
pendingCount -= 1;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const next = chain.then(run, run);
|
|
16
|
+
chain = next.catch(() => undefined);
|
|
17
|
+
return next;
|
|
18
|
+
},
|
|
19
|
+
get pending() {
|
|
20
|
+
return pendingCount;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function createTranslationQueueRegistry() {
|
|
25
|
+
const queues = new Map();
|
|
26
|
+
return {
|
|
27
|
+
getQueue(key) {
|
|
28
|
+
let queue = queues.get(key);
|
|
29
|
+
if (!queue) {
|
|
30
|
+
queue = createSerialTranslationQueue();
|
|
31
|
+
queues.set(key, queue);
|
|
32
|
+
}
|
|
33
|
+
return queue;
|
|
34
|
+
},
|
|
35
|
+
clearQueue(key) {
|
|
36
|
+
queues.delete(key);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|