@adhdev/daemon-core 0.9.82-rc.57 → 0.9.82-rc.59
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/dist/index.d.ts +6 -4
- package/dist/index.js +431 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +424 -66
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-active-work.d.ts +48 -0
- package/dist/mesh/mesh-ledger.d.ts +37 -0
- package/dist/mesh/mesh-work-queue.d.ts +12 -0
- package/dist/providers/cli-provider-instance.d.ts +1 -0
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/commands/router.ts +7 -1
- package/src/index.ts +6 -4
- package/src/mesh/mesh-active-work.ts +205 -0
- package/src/mesh/mesh-events.ts +42 -8
- package/src/mesh/mesh-ledger.ts +135 -0
- package/src/mesh/mesh-work-queue.ts +54 -1
- package/src/providers/cli-provider-instance.ts +66 -1
package/src/mesh/mesh-ledger.ts
CHANGED
|
@@ -63,6 +63,44 @@ export function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind
|
|
|
63
63
|
|| payload.source === 'mesh_remove_node');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export type MeshWorkerResultStatus = 'completed' | 'failed' | 'blocked' | 'partial' | 'unknown';
|
|
67
|
+
export type MeshProcessArtifactKind = 'process' | 'log' | 'port' | 'window' | 'session' | 'file' | 'url' | 'other';
|
|
68
|
+
|
|
69
|
+
export interface MeshValidationResultArtifact {
|
|
70
|
+
command?: string;
|
|
71
|
+
status: 'passed' | 'failed' | 'skipped' | 'unknown';
|
|
72
|
+
durationMs?: number;
|
|
73
|
+
outputPath?: string;
|
|
74
|
+
summary?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface MeshProcessArtifact {
|
|
78
|
+
kind: MeshProcessArtifactKind;
|
|
79
|
+
id?: string;
|
|
80
|
+
label?: string;
|
|
81
|
+
locator?: string;
|
|
82
|
+
pid?: number;
|
|
83
|
+
port?: number;
|
|
84
|
+
url?: string;
|
|
85
|
+
path?: string;
|
|
86
|
+
sessionId?: string;
|
|
87
|
+
keepRunning?: boolean;
|
|
88
|
+
metadata?: Record<string, unknown>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface MeshWorkerResultArtifact {
|
|
92
|
+
status: MeshWorkerResultStatus;
|
|
93
|
+
classification?: string;
|
|
94
|
+
changedFiles: string[];
|
|
95
|
+
validationResults: MeshValidationResultArtifact[];
|
|
96
|
+
gitStatus?: Record<string, unknown>;
|
|
97
|
+
processArtifacts: MeshProcessArtifact[];
|
|
98
|
+
errors: string[];
|
|
99
|
+
nextAction?: string;
|
|
100
|
+
requiresUserAction: boolean;
|
|
101
|
+
source: 'explicit_metadata' | 'final_summary_json' | 'default';
|
|
102
|
+
}
|
|
103
|
+
|
|
66
104
|
export interface MeshTaskCompletionEvidence {
|
|
67
105
|
source: 'agent_status_event';
|
|
68
106
|
event: 'agent:generating_completed' | 'agent:ready';
|
|
@@ -76,6 +114,7 @@ export interface MeshTaskCompletionEvidence {
|
|
|
76
114
|
providerSessionId?: string;
|
|
77
115
|
finalSummaryAvailable: boolean;
|
|
78
116
|
};
|
|
117
|
+
workerResult: MeshWorkerResultArtifact;
|
|
79
118
|
git: {
|
|
80
119
|
status: 'deferred';
|
|
81
120
|
reason: string;
|
|
@@ -98,6 +137,7 @@ export interface BuildTaskCompletionEvidenceOptions {
|
|
|
98
137
|
providerType?: string;
|
|
99
138
|
providerSessionId?: string;
|
|
100
139
|
finalSummary?: string;
|
|
140
|
+
workerResult?: Record<string, unknown>;
|
|
101
141
|
completedAt?: string;
|
|
102
142
|
}
|
|
103
143
|
|
|
@@ -190,6 +230,100 @@ function getRotatedPath(meshId: string, index: number): string {
|
|
|
190
230
|
|
|
191
231
|
// ─── Core API ───────────────────────────────────
|
|
192
232
|
|
|
233
|
+
function readNonEmptyString(value: unknown): string | undefined {
|
|
234
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function readStringArray(value: unknown): string[] {
|
|
238
|
+
if (!Array.isArray(value)) return [];
|
|
239
|
+
return value.map(item => readNonEmptyString(item)).filter(Boolean) as string[];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function extractJsonObjectFromSummary(summary?: string): Record<string, unknown> | undefined {
|
|
243
|
+
const text = readNonEmptyString(summary);
|
|
244
|
+
if (!text) return undefined;
|
|
245
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
246
|
+
const candidates = [fenced?.[1], text].filter(Boolean) as string[];
|
|
247
|
+
for (const candidate of candidates) {
|
|
248
|
+
const trimmed = candidate.trim();
|
|
249
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) continue;
|
|
250
|
+
try {
|
|
251
|
+
const parsed = JSON.parse(trimmed);
|
|
252
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed;
|
|
253
|
+
} catch { /* try next candidate */ }
|
|
254
|
+
}
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function normalizeValidationResults(value: unknown): MeshValidationResultArtifact[] {
|
|
259
|
+
if (!Array.isArray(value)) return [];
|
|
260
|
+
return value
|
|
261
|
+
.filter(item => item && typeof item === 'object' && !Array.isArray(item))
|
|
262
|
+
.map((item: any) => {
|
|
263
|
+
const status = ['passed', 'failed', 'skipped', 'unknown'].includes(item.status) ? item.status : 'unknown';
|
|
264
|
+
return {
|
|
265
|
+
...(readNonEmptyString(item.command) ? { command: readNonEmptyString(item.command) } : {}),
|
|
266
|
+
status,
|
|
267
|
+
...(Number.isFinite(Number(item.durationMs)) ? { durationMs: Number(item.durationMs) } : {}),
|
|
268
|
+
...(readNonEmptyString(item.outputPath) ? { outputPath: readNonEmptyString(item.outputPath) } : {}),
|
|
269
|
+
...(readNonEmptyString(item.summary) ? { summary: readNonEmptyString(item.summary) } : {}),
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function normalizeProcessArtifacts(value: unknown): MeshProcessArtifact[] {
|
|
275
|
+
if (!Array.isArray(value)) return [];
|
|
276
|
+
const kinds = new Set(['process', 'log', 'port', 'window', 'session', 'file', 'url', 'other']);
|
|
277
|
+
return value
|
|
278
|
+
.filter(item => item && typeof item === 'object' && !Array.isArray(item))
|
|
279
|
+
.map((item: any) => ({
|
|
280
|
+
kind: kinds.has(item.kind) ? item.kind : 'other',
|
|
281
|
+
...(readNonEmptyString(item.id) ? { id: readNonEmptyString(item.id) } : {}),
|
|
282
|
+
...(readNonEmptyString(item.label) ? { label: readNonEmptyString(item.label) } : {}),
|
|
283
|
+
...(readNonEmptyString(item.locator) ? { locator: readNonEmptyString(item.locator) } : {}),
|
|
284
|
+
...(Number.isFinite(Number(item.pid)) ? { pid: Number(item.pid) } : {}),
|
|
285
|
+
...(Number.isFinite(Number(item.port)) ? { port: Number(item.port) } : {}),
|
|
286
|
+
...(readNonEmptyString(item.url) ? { url: readNonEmptyString(item.url) } : {}),
|
|
287
|
+
...(readNonEmptyString(item.path) ? { path: readNonEmptyString(item.path) } : {}),
|
|
288
|
+
...(readNonEmptyString(item.sessionId) ? { sessionId: readNonEmptyString(item.sessionId) } : {}),
|
|
289
|
+
...(typeof item.keepRunning === 'boolean' ? { keepRunning: item.keepRunning } : {}),
|
|
290
|
+
...(item.metadata && typeof item.metadata === 'object' && !Array.isArray(item.metadata) ? { metadata: item.metadata as Record<string, unknown> } : {}),
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function normalizeMeshWorkerResult(input?: Record<string, unknown>, source: MeshWorkerResultArtifact['source'] = 'explicit_metadata'): MeshWorkerResultArtifact {
|
|
295
|
+
const raw = input && typeof input === 'object' ? input : {};
|
|
296
|
+
const status = ['completed', 'failed', 'blocked', 'partial', 'unknown'].includes(String(raw.status))
|
|
297
|
+
? raw.status as MeshWorkerResultStatus
|
|
298
|
+
: 'unknown';
|
|
299
|
+
const gitStatus = raw.gitStatus && typeof raw.gitStatus === 'object' && !Array.isArray(raw.gitStatus)
|
|
300
|
+
? raw.gitStatus as Record<string, unknown>
|
|
301
|
+
: undefined;
|
|
302
|
+
return {
|
|
303
|
+
status,
|
|
304
|
+
...(readNonEmptyString(raw.classification) ? { classification: readNonEmptyString(raw.classification) } : {}),
|
|
305
|
+
changedFiles: readStringArray(raw.changedFiles),
|
|
306
|
+
validationResults: normalizeValidationResults(raw.validationResults),
|
|
307
|
+
...(gitStatus ? { gitStatus } : {}),
|
|
308
|
+
processArtifacts: normalizeProcessArtifacts(raw.processArtifacts),
|
|
309
|
+
errors: readStringArray(raw.errors),
|
|
310
|
+
...(readNonEmptyString(raw.nextAction) ? { nextAction: readNonEmptyString(raw.nextAction) } : {}),
|
|
311
|
+
requiresUserAction: raw.requiresUserAction === true,
|
|
312
|
+
source,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function resolveWorkerResult(opts: BuildTaskCompletionEvidenceOptions): MeshWorkerResultArtifact {
|
|
317
|
+
if (opts.workerResult && typeof opts.workerResult === 'object') {
|
|
318
|
+
return normalizeMeshWorkerResult(opts.workerResult, 'explicit_metadata');
|
|
319
|
+
}
|
|
320
|
+
const parsed = extractJsonObjectFromSummary(opts.finalSummary);
|
|
321
|
+
if (parsed) {
|
|
322
|
+
return normalizeMeshWorkerResult(parsed, 'final_summary_json');
|
|
323
|
+
}
|
|
324
|
+
return normalizeMeshWorkerResult(undefined, 'default');
|
|
325
|
+
}
|
|
326
|
+
|
|
193
327
|
export function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence {
|
|
194
328
|
const providerSessionId = opts.providerSessionId?.trim() || undefined;
|
|
195
329
|
const providerType = opts.providerType?.trim() || undefined;
|
|
@@ -206,6 +340,7 @@ export function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOpt
|
|
|
206
340
|
providerSessionId,
|
|
207
341
|
finalSummaryAvailable: typeof opts.finalSummary === 'string' && opts.finalSummary.trim().length > 0,
|
|
208
342
|
},
|
|
343
|
+
workerResult: resolveWorkerResult(opts),
|
|
209
344
|
git: {
|
|
210
345
|
status: 'deferred',
|
|
211
346
|
reason: 'ordinary_completion_git_status_not_checked',
|
|
@@ -8,15 +8,63 @@ import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
|
|
|
8
8
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
9
9
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
10
10
|
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
11
|
+
export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
|
|
11
12
|
|
|
12
13
|
export const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[] = ['pending', 'assigned'];
|
|
13
14
|
export const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[] = ['completed', 'failed', 'cancelled'];
|
|
15
|
+
export const MESH_TASK_MODES: MeshTaskMode[] = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'];
|
|
16
|
+
|
|
17
|
+
export interface MeshTaskModeValidationResult {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
taskMode?: MeshTaskMode;
|
|
20
|
+
violations: string[];
|
|
21
|
+
allowedOperations?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const LIVE_DEBUG_READONLY_FORBIDDEN: Array<{ label: string; pattern: RegExp }> = [
|
|
25
|
+
{ label: 'source_edit', pattern: /\b(edit|modify|patch|apply\s+patch|write\s+(?:to\s+)?(?:file|source)|overwrite|delete\s+file|remove\s+file|create\s+file|touch\s+file)\b/i },
|
|
26
|
+
{ label: 'git_mutation', pattern: /\b(?:git\s+(?:add|commit|push|reset|rebase|clean|checkout|switch|merge|tag|restore|rm|mv)|push\b)/i },
|
|
27
|
+
{ label: 'checkpoint', pattern: /\b(checkpoint|mesh_checkpoint)\b/i },
|
|
28
|
+
{ label: 'deploy_or_version_bump', pattern: /\b(deploy|wrangler\s+deploy|version[-\s]?bump|npm\s+version|release)\b/i },
|
|
29
|
+
{ label: 'destructive_shell', pattern: /\b(rm\s+-rf|mv\s+\S+\s+\S+|truncate\s|tee\s+\S+|sed\s+-i)\b/i },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined {
|
|
33
|
+
if (typeof value !== 'string') return undefined;
|
|
34
|
+
const normalized = value.trim() as MeshTaskMode;
|
|
35
|
+
return (MESH_TASK_MODES as string[]).includes(normalized) ? normalized : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult {
|
|
39
|
+
const taskMode = normalizeMeshTaskMode(mode);
|
|
40
|
+
if (!taskMode) {
|
|
41
|
+
return { valid: true, violations: [] };
|
|
42
|
+
}
|
|
43
|
+
if (taskMode !== 'live_debug_readonly') {
|
|
44
|
+
return { valid: true, taskMode, violations: [] };
|
|
45
|
+
}
|
|
46
|
+
const violations = LIVE_DEBUG_READONLY_FORBIDDEN
|
|
47
|
+
.filter(rule => rule.pattern.test(message || ''))
|
|
48
|
+
.map(rule => rule.label);
|
|
49
|
+
return {
|
|
50
|
+
valid: violations.length === 0,
|
|
51
|
+
taskMode,
|
|
52
|
+
violations,
|
|
53
|
+
allowedOperations: [
|
|
54
|
+
'process/log/window/port/session inspection',
|
|
55
|
+
'read-only filesystem listing/reading',
|
|
56
|
+
'status probes and keep-running handle reporting',
|
|
57
|
+
'diagnostic summaries without source edits, commits, checkpoints, pushes, deploys, resets, rebases, or destructive cleanups',
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
14
61
|
|
|
15
62
|
export interface MeshWorkQueueEntry {
|
|
16
63
|
id: string;
|
|
17
64
|
meshId: string;
|
|
18
65
|
message: string;
|
|
19
66
|
status: MeshTaskStatus;
|
|
67
|
+
taskMode?: MeshTaskMode;
|
|
20
68
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
21
69
|
targetNodeId?: string;
|
|
22
70
|
/** If specified, only this runtime session can claim the task */
|
|
@@ -103,9 +151,13 @@ function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
|
103
151
|
export function enqueueTask(
|
|
104
152
|
meshId: string,
|
|
105
153
|
message: string,
|
|
106
|
-
opts?: { targetNodeId?: string; targetSessionId?: string } & MeshQueueMutationOptions,
|
|
154
|
+
opts?: { targetNodeId?: string; targetSessionId?: string; taskMode?: MeshTaskMode | string } & MeshQueueMutationOptions,
|
|
107
155
|
): MeshWorkQueueEntry {
|
|
108
156
|
requireMeshHostQueueOwner(opts);
|
|
157
|
+
const modeValidation = validateMeshTaskModeRequest(opts?.taskMode, message);
|
|
158
|
+
if (!modeValidation.valid) {
|
|
159
|
+
throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(', ')})`);
|
|
160
|
+
}
|
|
109
161
|
return withQueueLock(meshId, () => {
|
|
110
162
|
const queue = readQueue(meshId);
|
|
111
163
|
const entry: MeshWorkQueueEntry = {
|
|
@@ -113,6 +165,7 @@ export function enqueueTask(
|
|
|
113
165
|
meshId,
|
|
114
166
|
message,
|
|
115
167
|
status: 'pending',
|
|
168
|
+
taskMode: modeValidation.taskMode,
|
|
116
169
|
targetNodeId: opts?.targetNodeId,
|
|
117
170
|
targetSessionId: opts?.targetSessionId,
|
|
118
171
|
createdAt: new Date().toISOString(),
|
|
@@ -743,6 +743,55 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
743
743
|
return role === 'assistant' && !!content;
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
+
private buildCompletedFinalizationDiagnostic(args: {
|
|
747
|
+
blockReason: string;
|
|
748
|
+
latestStatus?: any;
|
|
749
|
+
latestVisibleStatus: string;
|
|
750
|
+
waitedMs: number;
|
|
751
|
+
pending: CompletedDebouncePending;
|
|
752
|
+
emittedAfterFinalizationTimeout: boolean;
|
|
753
|
+
}): Record<string, unknown> {
|
|
754
|
+
let parsed: any = null;
|
|
755
|
+
let parseError: string | undefined;
|
|
756
|
+
try {
|
|
757
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
758
|
+
} catch (error: any) {
|
|
759
|
+
parseError = error?.message || String(error);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const visibleMessages = (Array.isArray(parsed?.messages) ? parsed.messages : [])
|
|
763
|
+
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
764
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
765
|
+
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
766
|
+
const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
|
|
767
|
+
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
providerType: this.type,
|
|
771
|
+
sessionId: this.instanceId,
|
|
772
|
+
providerSessionId: this.providerSessionId || null,
|
|
773
|
+
workspace: this.workingDir,
|
|
774
|
+
blockReason: args.blockReason,
|
|
775
|
+
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
776
|
+
waitedMs: args.waitedMs,
|
|
777
|
+
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
778
|
+
adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
|
|
779
|
+
latestVisibleStatus: args.latestVisibleStatus,
|
|
780
|
+
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
781
|
+
parseError: parseError || undefined,
|
|
782
|
+
finalAssistantPresent: this.completionHasFinalAssistantMessage(parsed?.messages),
|
|
783
|
+
visibleMessageCount: visibleMessages.length,
|
|
784
|
+
lastVisibleRole,
|
|
785
|
+
lastVisibleKind,
|
|
786
|
+
lastVisibleContentLength,
|
|
787
|
+
pendingStartedAt: this.generatingStartedAt || null,
|
|
788
|
+
pendingFirstObservedAt: args.pending.firstObservedAt,
|
|
789
|
+
pendingTimestamp: args.pending.timestamp,
|
|
790
|
+
pendingDurationSec: args.pending.duration,
|
|
791
|
+
previousBlockReason: args.pending.loggedBlockReason || null,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
746
795
|
private hasAdapterPendingResponse(): boolean {
|
|
747
796
|
const adapterAny = this.adapter as any;
|
|
748
797
|
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
@@ -828,7 +877,23 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
877
|
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
829
878
|
return;
|
|
830
879
|
}
|
|
831
|
-
|
|
880
|
+
const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
|
|
881
|
+
blockReason,
|
|
882
|
+
latestStatus,
|
|
883
|
+
latestVisibleStatus,
|
|
884
|
+
waitedMs,
|
|
885
|
+
pending,
|
|
886
|
+
emittedAfterFinalizationTimeout: true,
|
|
887
|
+
});
|
|
888
|
+
LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
889
|
+
this.pushEvent({
|
|
890
|
+
event: 'agent:generating_completed',
|
|
891
|
+
chatTitle: pending.chatTitle,
|
|
892
|
+
duration: pending.duration,
|
|
893
|
+
timestamp: pending.timestamp,
|
|
894
|
+
finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
|
|
895
|
+
completionDiagnostic,
|
|
896
|
+
});
|
|
832
897
|
this.completedDebouncePending = null;
|
|
833
898
|
this.completedDebounceTimer = null;
|
|
834
899
|
this.generatingStartedAt = 0;
|