@adhdev/daemon-core 0.9.82-rc.442 → 0.9.82-rc.444
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/commands/high-family/types.d.ts +4 -0
- package/dist/commands/router.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +551 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +543 -63
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-ledger.d.ts +39 -1
- package/dist/mesh/mesh-node-identity.d.ts +15 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +15 -1
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +28 -2
- package/package.json +2 -2
- package/src/commands/chat-commands-read.ts +41 -0
- package/src/commands/high-family/mesh-coordinator-launch.ts +4 -2
- package/src/commands/high-family/mesh-events.ts +11 -2
- package/src/commands/high-family/mesh-status.ts +102 -9
- package/src/commands/high-family/types.ts +5 -1
- package/src/commands/router.ts +19 -2
- package/src/index.ts +1 -1
- package/src/mesh/coordinator-prompt.ts +1 -0
- package/src/mesh/mesh-ledger.ts +185 -0
- package/src/mesh/mesh-node-identity.ts +106 -18
- package/src/mesh/mesh-reconcile-loop.ts +22 -1
- package/src/providers/chat-message-normalization.ts +1 -1
- package/src/providers/cli-provider-instance.ts +130 -17
- package/src/providers/native-history/antigravity-cli-transcript.ts +348 -25
- package/src/providers/native-history/dispatcher.ts +33 -14
|
@@ -29,7 +29,7 @@ import { formatAutoApprovalMessage, pickApprovalButton, hasNegativeApprovalOptio
|
|
|
29
29
|
import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
|
|
30
30
|
import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
|
|
31
31
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
32
|
-
import { buildChatMessage, buildRuntimeSystemChatMessage, isUserFacingChatMessage, normalizeChatMessages, resolveChatMessageKind, extractFinalSummaryFromMessages, extractFinalSummaryFromMessagesAfter } from './chat-message-normalization.js';
|
|
32
|
+
import { buildChatMessage, buildRuntimeSystemChatMessage, isUserFacingChatMessage, normalizeChatMessages, resolveChatMessageKind, extractFinalSummaryFromMessages, extractFinalSummaryFromMessagesAfter, readChatMessageTimestampMs } from './chat-message-normalization.js';
|
|
33
33
|
import { workingDirBasename } from './working-dir.js';
|
|
34
34
|
import { ManualAttendanceTracker } from './manual-attendance.js';
|
|
35
35
|
|
|
@@ -75,6 +75,16 @@ type CompletedDebouncePending = {
|
|
|
75
75
|
// debounce that flushes before the producing turn's final assistant bubble lands
|
|
76
76
|
// in the native transcript never echoes the PRIOR task's last bubble.
|
|
77
77
|
turnStartedAt?: number;
|
|
78
|
+
// FALSE-IDLE continuity: the busyEpoch value at the instant this pending was armed.
|
|
79
|
+
// The flush guard requires this.busyEpoch to still equal this — proving no busy
|
|
80
|
+
// phase (generating/waiting_approval) opened since arming. A momentary busy→idle
|
|
81
|
+
// blip in an inter-approval valley bumps busyEpoch, so a completion armed before
|
|
82
|
+
// the blip is cancelled at flush instead of emitting a stale mid-turn summary.
|
|
83
|
+
busyEpochAtArm?: number;
|
|
84
|
+
// FALSE-IDLE continuity: the adapter's raw PTY lastOutputAt at arm time. New PTY
|
|
85
|
+
// output after arming means the session was not continuously idle through the
|
|
86
|
+
// settle window (the agent kept printing), so the completion is cancelled.
|
|
87
|
+
lastOutputAtArm?: number;
|
|
78
88
|
};
|
|
79
89
|
|
|
80
90
|
function isIdleStatus(value: unknown): boolean {
|
|
@@ -508,6 +518,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
508
518
|
// first sets it; the other becomes a no-op.
|
|
509
519
|
private agentReadyEmitted = false;
|
|
510
520
|
private generatingStartedAt: number = 0;
|
|
521
|
+
// FALSE-IDLE continuity epoch: monotonically bumped on EVERY entry into a busy
|
|
522
|
+
// phase (→generating or →waiting_approval). The completedDebouncePending snapshots
|
|
523
|
+
// this value at arm time (busyEpochAtArm); the flush guard requires it UNCHANGED
|
|
524
|
+
// — proving the session did not re-enter a busy phase (a momentary busy→idle blip
|
|
525
|
+
// in an inter-approval valley) between arming the debounce and flushing it. A
|
|
526
|
+
// single point-sample of status at flush time cannot see a generating phase that
|
|
527
|
+
// opened AND closed within the settle window; the epoch can. See
|
|
528
|
+
// flushCompletedDebounceIfFinalized.
|
|
529
|
+
private busyEpoch: number = 0;
|
|
511
530
|
// GENERATING-BOUNDARY (R4b): the per-turn taskId for which a startup-grace
|
|
512
531
|
// started+completed pair was already synthesized. Both fast-collapse callers
|
|
513
532
|
// (starting→idle transition AND the idle-stayed no-status-change poll) route
|
|
@@ -1188,8 +1207,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1188
1207
|
* to the genuine-modal classification.
|
|
1189
1208
|
*/
|
|
1190
1209
|
private isTransientToolConsent(now = Date.now()): boolean {
|
|
1191
|
-
|
|
1192
|
-
return isAutonomousMeshSession
|
|
1210
|
+
return this.isAutonomousMeshSession()
|
|
1193
1211
|
&& this.hasAdapterPendingResponse()
|
|
1194
1212
|
&& !this.manualAttendance.isAttended(now);
|
|
1195
1213
|
}
|
|
@@ -1411,7 +1429,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1411
1429
|
this.applyProviderResponse(parsed.payload, { phase: 'immediate' });
|
|
1412
1430
|
}
|
|
1413
1431
|
|
|
1414
|
-
private completionHasFinalAssistantMessage(messages: unknown): boolean {
|
|
1432
|
+
private completionHasFinalAssistantMessage(messages: unknown, turnStartedAt?: number): boolean {
|
|
1415
1433
|
const visibleMessages = (Array.isArray(messages) ? messages : [])
|
|
1416
1434
|
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
1417
1435
|
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
@@ -1421,6 +1439,21 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1421
1439
|
// Guard: if the last assistant message looks like an active approval/input prompt,
|
|
1422
1440
|
// it is not a real completion — the session is still awaiting user input.
|
|
1423
1441
|
if (looksLikeActiveApprovalPromptText(content)) return false;
|
|
1442
|
+
// FALSE-IDLE turn-boundary evidence (Defect 1b): when a producing-turn start is
|
|
1443
|
+
// known, the final assistant bubble must POST-DATE it. A STALE mid-turn assistant
|
|
1444
|
+
// (predating this turn's start — e.g. the last bubble of a prior sub-turn observed
|
|
1445
|
+
// during an inter-approval valley) must NOT satisfy the finalization gate, or a
|
|
1446
|
+
// false-idle blip emits a completion carrying that stale summary. A bubble with no
|
|
1447
|
+
// parseable timestamp cannot be proven stale, so it is kept (fails open — behaviour
|
|
1448
|
+
// identical to before for providers/paths that carry no timestamps).
|
|
1449
|
+
if (typeof turnStartedAt === 'number' && Number.isFinite(turnStartedAt) && turnStartedAt > 0) {
|
|
1450
|
+
// readChatMessageTimestampMs mirrors the summary turn-scoping reader
|
|
1451
|
+
// (extractFinalSummaryFromMessagesAfter) — same seconds-vs-ms heuristic and
|
|
1452
|
+
// field precedence — so the present-check and the summary-scope agree on which
|
|
1453
|
+
// bubbles predate the turn.
|
|
1454
|
+
const ts = readChatMessageTimestampMs(lastVisible);
|
|
1455
|
+
if (typeof ts === 'number' && ts < turnStartedAt) return false;
|
|
1456
|
+
}
|
|
1424
1457
|
return true;
|
|
1425
1458
|
}
|
|
1426
1459
|
|
|
@@ -1485,8 +1518,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1485
1518
|
return restoredHistory.messages;
|
|
1486
1519
|
}
|
|
1487
1520
|
|
|
1488
|
-
private completionFinalAssistantEvidence(parsedMessages: unknown): CompletionFinalAssistantEvidence {
|
|
1489
|
-
if (this.completionHasFinalAssistantMessage(parsedMessages)) {
|
|
1521
|
+
private completionFinalAssistantEvidence(parsedMessages: unknown, turnStartedAt?: number): CompletionFinalAssistantEvidence {
|
|
1522
|
+
if (this.completionHasFinalAssistantMessage(parsedMessages, turnStartedAt)) {
|
|
1490
1523
|
return {
|
|
1491
1524
|
present: true,
|
|
1492
1525
|
messages: Array.isArray(parsedMessages) ? parsedMessages : [],
|
|
@@ -1497,7 +1530,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1497
1530
|
const externalMessages = this.readExternalCompletionMessages();
|
|
1498
1531
|
if (externalMessages) {
|
|
1499
1532
|
return {
|
|
1500
|
-
present: this.completionHasFinalAssistantMessage(externalMessages),
|
|
1533
|
+
present: this.completionHasFinalAssistantMessage(externalMessages, turnStartedAt),
|
|
1501
1534
|
messages: externalMessages,
|
|
1502
1535
|
source: 'external-native',
|
|
1503
1536
|
};
|
|
@@ -1528,10 +1561,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1528
1561
|
// at/after turnStartedAt yields '' in that race instead of the stale tail; the weak/empty
|
|
1529
1562
|
// summary is later upgraded by the mesh reconcile loop once the real bubble is written.
|
|
1530
1563
|
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
1531
|
-
|
|
1532
|
-
|
|
1564
|
+
// FALSE-IDLE Defect 1b: turn-scope the PARSED screen fallback too. Without this a stale
|
|
1565
|
+
// mid-turn assistant (predating turnStartedAt) that the turn-boundary gate already
|
|
1566
|
+
// rejected as evidence could still leak into the finalSummary via this parsed fallback
|
|
1567
|
+
// when the external transcript's turn-scoped read is empty — freezing the very stale text
|
|
1568
|
+
// the gate rejected. extractFinalSummaryFromMessagesAfter drops bubbles before the turn
|
|
1569
|
+
// start; with no boundary known (turnStartedAt falsy) it is identical to the unscoped read.
|
|
1570
|
+
const parsedSummary = extractFinalSummaryFromMessagesAfter(
|
|
1571
|
+
(this.completionHasFinalAssistantMessage(parsedMessages, turnStartedAt)
|
|
1533
1572
|
? (Array.isArray(parsedMessages) ? parsedMessages : [])
|
|
1534
1573
|
: []) as any,
|
|
1574
|
+
turnStartedAt,
|
|
1535
1575
|
);
|
|
1536
1576
|
if (adapterOwnsMessagesElsewhere) {
|
|
1537
1577
|
const externalMessages = this.readExternalCompletionMessages();
|
|
@@ -1669,7 +1709,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1669
1709
|
}
|
|
1670
1710
|
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
1671
1711
|
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
1672
|
-
|
|
1712
|
+
// FALSE-IDLE turn-boundary evidence (Defect 1b): turn-scope the present-check so a
|
|
1713
|
+
// STALE mid-turn assistant (predating pending.turnStartedAt) cannot satisfy the
|
|
1714
|
+
// finalization gate. Only the confirming final-assistant bubble that POST-DATES this
|
|
1715
|
+
// turn's start counts as evidence the turn genuinely ended.
|
|
1716
|
+
const finalAssistantEvidence = this.completionFinalAssistantEvidence(parsed?.messages, pending.turnStartedAt);
|
|
1673
1717
|
const allowMissingAssistantTimeout = !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.launchedByCoordinator);
|
|
1674
1718
|
LOG.debug('CLI', `[${this.type}] finalAssistantEvidence: present=${finalAssistantEvidence.present} source=${finalAssistantEvidence.source} adapterOwnsMessagesElsewhere=${adapterOwnsMessagesElsewhere} parsedStatus=${parsedStatus}`);
|
|
1675
1719
|
if (!finalAssistantEvidence.present) {
|
|
@@ -1796,6 +1840,20 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1796
1840
|
|| this.settings.meshNodeId || this.settings.launchedByCoordinator);
|
|
1797
1841
|
}
|
|
1798
1842
|
|
|
1843
|
+
// FALSE-IDLE (self-coordinator settle): an autonomously-progressing mesh session
|
|
1844
|
+
// is either a delegated worker (isMeshWorkerSession) OR the coordinator's OWN
|
|
1845
|
+
// claude-cli session (meshCoordinatorFor). Both run auto-approved tool turns whose
|
|
1846
|
+
// inter-approval valley (busy→idle blip→generating re-entry ~0.5s later) must be
|
|
1847
|
+
// absorbed by the completedDebounce settle window, not flushed on the first idle
|
|
1848
|
+
// sample. The worker branch already gets NATIVE_HISTORY_MESH_IDLE_SETTLE_MS; the
|
|
1849
|
+
// self-coordinator session (worker markers absent, meshCoordinatorFor present) was
|
|
1850
|
+
// taking flushDelay=0 — no settle window — so its busyEpoch/lastOutputAt continuity
|
|
1851
|
+
// guard had no window to observe the valley and fired mid-turn "next-step" previews
|
|
1852
|
+
// as a finalSummary. Mirrors the isAutonomousMeshSession notion in isTransientToolConsent.
|
|
1853
|
+
private isAutonomousMeshSession(): boolean {
|
|
1854
|
+
return this.isMeshWorkerSession() || !!this.settings.meshCoordinatorFor;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1799
1857
|
/**
|
|
1800
1858
|
* ARCH-REFACTOR R1: the taskId to attribute the CURRENTLY-completing turn to.
|
|
1801
1859
|
* Prefers the per-turn binding (engine.currentTurnTaskId, set when the turn was
|
|
@@ -1844,6 +1902,32 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1844
1902
|
return;
|
|
1845
1903
|
}
|
|
1846
1904
|
|
|
1905
|
+
// FALSE-IDLE continuity guard (Defect 1a): the point-sample above only proves the
|
|
1906
|
+
// session is idle at THIS instant. A momentary busy→idle blip inside an inter-approval
|
|
1907
|
+
// valley (auto-approved tool turns) opens AND closes a generating phase entirely within
|
|
1908
|
+
// the settle window — so the single sample reads 'idle' even though the turn is still in
|
|
1909
|
+
// flight (it re-enters generating ~0.5s later). Require instead that the session stayed
|
|
1910
|
+
// CONTINUOUSLY idle since the debounce was armed: (1) no entry into a busy phase
|
|
1911
|
+
// (busyEpoch unchanged), and (2) no new raw PTY output (lastOutputAt did not advance).
|
|
1912
|
+
// Either signal ⇒ the idle was not continuous ⇒ cancel; the still-live turn re-arms its
|
|
1913
|
+
// own completion when it genuinely finishes. This only ever cancels (never emits more),
|
|
1914
|
+
// so shared behaviour for claude/codex/antigravity is strictly stricter, never looser.
|
|
1915
|
+
if (typeof pending.busyEpochAtArm === 'number' && this.busyEpoch !== pending.busyEpochAtArm) {
|
|
1916
|
+
LOG.info('CLI', `[${this.type}] cancelled pending completed (busy re-entry during settle: epoch ${pending.busyEpochAtArm}→${this.busyEpoch})`);
|
|
1917
|
+
this.completedDebouncePending = null;
|
|
1918
|
+
this.completedDebounceTimer = null;
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
const latestOutputAt = typeof (latestStatus as any)?.lastOutputAt === 'number' ? (latestStatus as any).lastOutputAt as number : undefined;
|
|
1922
|
+
if (typeof pending.lastOutputAtArm === 'number'
|
|
1923
|
+
&& typeof latestOutputAt === 'number'
|
|
1924
|
+
&& latestOutputAt > pending.lastOutputAtArm) {
|
|
1925
|
+
LOG.info('CLI', `[${this.type}] cancelled pending completed (new PTY output during settle: ${pending.lastOutputAtArm}→${latestOutputAt})`);
|
|
1926
|
+
this.completedDebouncePending = null;
|
|
1927
|
+
this.completedDebounceTimer = null;
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1847
1931
|
const block = this.getCompletedFinalizationBlock(latestVisibleStatus, pending);
|
|
1848
1932
|
if (block) {
|
|
1849
1933
|
const blockReason = block.reason;
|
|
@@ -2357,6 +2441,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2357
2441
|
}
|
|
2358
2442
|
|
|
2359
2443
|
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
2444
|
+
// FALSE-IDLE continuity: entering a busy phase invalidates any
|
|
2445
|
+
// completedDebouncePending armed earlier in this settle window.
|
|
2446
|
+
this.busyEpoch++;
|
|
2360
2447
|
// Defer the generating_started event — if idle comes back within 3s,
|
|
2361
2448
|
// the whole started→completed pair was a false positive from PTY noise
|
|
2362
2449
|
if (this.generatingDebounceTimer) clearTimeout(this.generatingDebounceTimer);
|
|
@@ -2381,6 +2468,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2381
2468
|
this.completedDebouncePending = null;
|
|
2382
2469
|
|
|
2383
2470
|
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
2471
|
+
// FALSE-IDLE continuity: waiting_approval is a busy phase (the agent
|
|
2472
|
+
// resumes into it), so bump the epoch too — the completedDebouncePending
|
|
2473
|
+
// cancel above covers the currently-armed pending, and the epoch covers
|
|
2474
|
+
// a pending that re-arms and flushes across this same valley.
|
|
2475
|
+
this.busyEpoch++;
|
|
2384
2476
|
const modal = adapterStatus.activeModal;
|
|
2385
2477
|
LOG.info('CLI', `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? 'none'}"`);
|
|
2386
2478
|
// Include the FSM's approval entry seq, mirroring the auto-approve
|
|
@@ -2533,19 +2625,40 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2533
2625
|
const turnStartedAt = engineTurnStart || this.generatingStartedAt || 0;
|
|
2534
2626
|
return turnStartedAt ? { turnStartedAt } : {};
|
|
2535
2627
|
})()),
|
|
2628
|
+
// FALSE-IDLE continuity: snapshot the busy epoch + raw PTY output
|
|
2629
|
+
// clock at arm time so the flush guard can prove the session stayed
|
|
2630
|
+
// continuously idle (no busy re-entry, no new PTY output) through the
|
|
2631
|
+
// settle window rather than merely reading 'idle' once at flush.
|
|
2632
|
+
busyEpochAtArm: this.busyEpoch,
|
|
2633
|
+
...(typeof adapterStatus?.lastOutputAt === 'number' && Number.isFinite(adapterStatus.lastOutputAt)
|
|
2634
|
+
? { lastOutputAtArm: adapterStatus.lastOutputAt as number }
|
|
2635
|
+
: {}),
|
|
2536
2636
|
};
|
|
2537
2637
|
const ownsExternalHistory = !!(this.adapter as any)?.chatMessagesOwnedExternally;
|
|
2538
2638
|
// (FALSEIDLE-BGCHILD-a) Native-history providers flush immediately (the
|
|
2539
|
-
// transcript is authoritative). For mesh
|
|
2540
|
-
// generating→idle transition a short settle window so a background-child
|
|
2639
|
+
// transcript is authoritative). For autonomously-progressing mesh sessions,
|
|
2640
|
+
// give the generating→idle transition a short settle window so a background-child
|
|
2541
2641
|
// false idle (quiet after a backgrounded test/command while the parent turn
|
|
2542
|
-
// continues) gets caught by the resume
|
|
2543
|
-
// instead of firing an early completion
|
|
2544
|
-
|
|
2642
|
+
// continues) or an inter-approval auto-approve valley gets caught by the resume
|
|
2643
|
+
// guard in flushCompletedDebounceIfFinalized instead of firing an early completion
|
|
2644
|
+
// the coordinator can never correct.
|
|
2645
|
+
//
|
|
2646
|
+
// (FALSE-IDLE self-coordinator settle) The settle window now covers BOTH mesh
|
|
2647
|
+
// worker sessions AND the coordinator's own claude-cli session (meshCoordinatorFor):
|
|
2648
|
+
// isAutonomousMeshSession(). Previously only isMeshWorkerSession() qualified, so a
|
|
2649
|
+
// self-coordinating daemon (worker + coordinator on the same daemon) ran the
|
|
2650
|
+
// coordinator's own turn at flushDelay=0 — no settle window at all — and the
|
|
2651
|
+
// busyEpoch/lastOutputAt continuity guard, being a flush-time point-check, had no
|
|
2652
|
+
// window in which to observe the ~0.5s auto-approve valley. Its mid-turn
|
|
2653
|
+
// "next-step" sentence was flushed as finalSummary. A genuinely non-mesh session
|
|
2654
|
+
// (neither worker nor self-coordinator) still flushes immediately (delay=0), so
|
|
2655
|
+
// no non-mesh behaviour changes; this only ADDS a settle window (strictly
|
|
2656
|
+
// stricter — the guard can only ever CANCEL a pending flush, never emit more).
|
|
2657
|
+
const meshSettleSession = this.isAutonomousMeshSession();
|
|
2545
2658
|
const flushDelay = ownsExternalHistory
|
|
2546
|
-
? (
|
|
2659
|
+
? (meshSettleSession ? NATIVE_HISTORY_MESH_IDLE_SETTLE_MS : 0)
|
|
2547
2660
|
: 3000;
|
|
2548
|
-
LOG.debug('CLI', `[${this.type}] set completedDebouncePending duration=${duration}s ownsExternalHistory=${ownsExternalHistory}
|
|
2661
|
+
LOG.debug('CLI', `[${this.type}] set completedDebouncePending duration=${duration}s ownsExternalHistory=${ownsExternalHistory} meshSettle=${meshSettleSession} flushDelay=${flushDelay}ms generatingStartedAt=${this.generatingStartedAt}`);
|
|
2549
2662
|
this.scheduleCompletedDebounceFlush(flushDelay);
|
|
2550
2663
|
}
|
|
2551
2664
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|