@dungle-scrubs/tallow 0.8.21 → 0.8.23
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/cli.js +35 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +2 -0
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +82 -0
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/sdk.d.ts +17 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +68 -1
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-relay.d.ts +40 -7
- package/dist/workspace-transition-relay.d.ts.map +1 -1
- package/dist/workspace-transition-relay.js +81 -16
- package/dist/workspace-transition-relay.js.map +1 -1
- package/extensions/__integration__/background-task-widget-ownership.test.ts +216 -0
- package/extensions/__integration__/claude-hooks-compat.test.ts +156 -0
- package/extensions/__integration__/slash-command-bridge.test.ts +169 -23
- package/extensions/_shared/atomic-write.ts +1 -1
- package/extensions/_shared/bordered-box.ts +102 -0
- package/extensions/_shared/interop-events.ts +5 -0
- package/extensions/_shared/pid-registry.ts +1 -1
- package/extensions/agent-commands-tool/index.ts +4 -1
- package/extensions/background-task-tool/__tests__/lifecycle.test.ts +50 -25
- package/extensions/background-task-tool/index.ts +139 -221
- package/extensions/bash-tool-enhanced/index.ts +1 -75
- package/extensions/cd-tool/index.ts +2 -2
- package/extensions/context-fork/spawn.ts +4 -1
- package/extensions/health/index.ts +6 -6
- package/extensions/hooks/__tests__/claude-compat.test.ts +35 -0
- package/extensions/hooks/__tests__/subprocess-hardening.test.ts +73 -0
- package/extensions/hooks/index.ts +27 -4
- package/extensions/loop/__tests__/loop.test.ts +168 -4
- package/extensions/loop/extension.json +6 -5
- package/extensions/loop/index.ts +242 -31
- package/extensions/plan-mode-tool/__tests__/agent-end-execution.test.ts +373 -0
- package/extensions/plan-mode-tool/index.ts +103 -41
- package/extensions/prompt-suggestions/__tests__/editor-compatibility.test.ts +42 -0
- package/extensions/prompt-suggestions/index.ts +41 -6
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +267 -671
- package/extensions/slash-command-bridge/extension.json +1 -1
- package/extensions/slash-command-bridge/index.ts +230 -116
- package/extensions/subagent-tool/index.ts +2 -2
- package/extensions/subagent-tool/process.ts +4 -5
- package/extensions/tasks/commands/register-tasks-extension.ts +41 -0
- package/extensions/teams-tool/__tests__/peer-messaging.test.ts +29 -24
- package/extensions/teams-tool/dashboard.ts +3 -5
- package/extensions/teams-tool/dispatch/auto-dispatch.ts +18 -1
- package/extensions/teams-tool/tools/teammate-tools.ts +9 -6
- package/extensions/wezterm-pane-control/__tests__/index.test.ts +88 -4
- package/extensions/wezterm-pane-control/index.ts +113 -8
- package/package.json +6 -4
- package/packages/tallow-tui/README.md +51 -0
- package/packages/tallow-tui/dist/autocomplete.d.ts +48 -0
- package/packages/tallow-tui/dist/autocomplete.d.ts.map +1 -0
- package/packages/tallow-tui/dist/autocomplete.js +564 -0
- package/packages/tallow-tui/dist/autocomplete.js.map +1 -0
- package/packages/tallow-tui/dist/border-styles.d.ts +32 -0
- package/packages/tallow-tui/dist/border-styles.d.ts.map +1 -0
- package/packages/tallow-tui/dist/border-styles.js +46 -0
- package/packages/tallow-tui/dist/border-styles.js.map +1 -0
- package/packages/tallow-tui/dist/components/bordered-box.d.ts +52 -0
- package/packages/tallow-tui/dist/components/bordered-box.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/bordered-box.js +89 -0
- package/packages/tallow-tui/dist/components/bordered-box.js.map +1 -0
- package/packages/tallow-tui/dist/components/box.d.ts +22 -0
- package/packages/tallow-tui/dist/components/box.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/box.js +104 -0
- package/packages/tallow-tui/dist/components/box.js.map +1 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.d.ts +22 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.js +35 -0
- package/packages/tallow-tui/dist/components/cancellable-loader.js.map +1 -0
- package/packages/tallow-tui/dist/components/editor.d.ts +240 -0
- package/packages/tallow-tui/dist/components/editor.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/editor.js +1766 -0
- package/packages/tallow-tui/dist/components/editor.js.map +1 -0
- package/packages/tallow-tui/dist/components/image.d.ts +126 -0
- package/packages/tallow-tui/dist/components/image.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/image.js +245 -0
- package/packages/tallow-tui/dist/components/image.js.map +1 -0
- package/packages/tallow-tui/dist/components/input.d.ts +37 -0
- package/packages/tallow-tui/dist/components/input.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/input.js +439 -0
- package/packages/tallow-tui/dist/components/input.js.map +1 -0
- package/packages/tallow-tui/dist/components/loader.d.ts +88 -0
- package/packages/tallow-tui/dist/components/loader.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/loader.js +146 -0
- package/packages/tallow-tui/dist/components/loader.js.map +1 -0
- package/packages/tallow-tui/dist/components/markdown.d.ts +95 -0
- package/packages/tallow-tui/dist/components/markdown.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/markdown.js +633 -0
- package/packages/tallow-tui/dist/components/markdown.js.map +1 -0
- package/packages/tallow-tui/dist/components/select-list.d.ts +32 -0
- package/packages/tallow-tui/dist/components/select-list.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/select-list.js +156 -0
- package/packages/tallow-tui/dist/components/select-list.js.map +1 -0
- package/packages/tallow-tui/dist/components/settings-list.d.ts +50 -0
- package/packages/tallow-tui/dist/components/settings-list.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/settings-list.js +189 -0
- package/packages/tallow-tui/dist/components/settings-list.js.map +1 -0
- package/packages/tallow-tui/dist/components/spacer.d.ts +12 -0
- package/packages/tallow-tui/dist/components/spacer.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/spacer.js +23 -0
- package/packages/tallow-tui/dist/components/spacer.js.map +1 -0
- package/packages/tallow-tui/dist/components/text.d.ts +19 -0
- package/packages/tallow-tui/dist/components/text.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/text.js +91 -0
- package/packages/tallow-tui/dist/components/text.js.map +1 -0
- package/packages/tallow-tui/dist/components/truncated-text.d.ts +13 -0
- package/packages/tallow-tui/dist/components/truncated-text.d.ts.map +1 -0
- package/packages/tallow-tui/dist/components/truncated-text.js +51 -0
- package/packages/tallow-tui/dist/components/truncated-text.js.map +1 -0
- package/packages/tallow-tui/dist/editor-component.d.ts +50 -0
- package/packages/tallow-tui/dist/editor-component.d.ts.map +1 -0
- package/packages/tallow-tui/dist/editor-component.js +2 -0
- package/packages/tallow-tui/dist/editor-component.js.map +1 -0
- package/packages/tallow-tui/dist/fuzzy.d.ts +16 -0
- package/packages/tallow-tui/dist/fuzzy.d.ts.map +1 -0
- package/packages/tallow-tui/dist/fuzzy.js +107 -0
- package/packages/tallow-tui/dist/fuzzy.js.map +1 -0
- package/packages/tallow-tui/dist/index.d.ts +25 -0
- package/packages/tallow-tui/dist/index.d.ts.map +1 -0
- package/packages/tallow-tui/dist/index.js +35 -0
- package/packages/tallow-tui/dist/index.js.map +1 -0
- package/packages/tallow-tui/dist/keybindings.d.ts +39 -0
- package/packages/tallow-tui/dist/keybindings.d.ts.map +1 -0
- package/packages/tallow-tui/dist/keybindings.js +114 -0
- package/packages/tallow-tui/dist/keybindings.js.map +1 -0
- package/packages/tallow-tui/dist/keys.d.ts +168 -0
- package/packages/tallow-tui/dist/keys.d.ts.map +1 -0
- package/packages/tallow-tui/dist/keys.js +971 -0
- package/packages/tallow-tui/dist/keys.js.map +1 -0
- package/packages/tallow-tui/dist/kill-ring.d.ts +28 -0
- package/packages/tallow-tui/dist/kill-ring.d.ts.map +1 -0
- package/packages/tallow-tui/dist/kill-ring.js +44 -0
- package/packages/tallow-tui/dist/kill-ring.js.map +1 -0
- package/packages/tallow-tui/dist/stdin-buffer.d.ts +48 -0
- package/packages/tallow-tui/dist/stdin-buffer.d.ts.map +1 -0
- package/packages/tallow-tui/dist/stdin-buffer.js +317 -0
- package/packages/tallow-tui/dist/stdin-buffer.js.map +1 -0
- package/packages/tallow-tui/dist/terminal-image.d.ts +161 -0
- package/packages/tallow-tui/dist/terminal-image.d.ts.map +1 -0
- package/packages/tallow-tui/dist/terminal-image.js +460 -0
- package/packages/tallow-tui/dist/terminal-image.js.map +1 -0
- package/packages/tallow-tui/dist/terminal.d.ts +102 -0
- package/packages/tallow-tui/dist/terminal.d.ts.map +1 -0
- package/packages/tallow-tui/dist/terminal.js +263 -0
- package/packages/tallow-tui/dist/terminal.js.map +1 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.d.ts +14 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.d.ts.map +1 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.js +55 -0
- package/packages/tallow-tui/dist/test-utils/capability-env.js.map +1 -0
- package/packages/tallow-tui/dist/tui.d.ts +239 -0
- package/packages/tallow-tui/dist/tui.d.ts.map +1 -0
- package/packages/tallow-tui/dist/tui.js +1058 -0
- package/packages/tallow-tui/dist/tui.js.map +1 -0
- package/packages/tallow-tui/dist/undo-stack.d.ts +17 -0
- package/packages/tallow-tui/dist/undo-stack.d.ts.map +1 -0
- package/packages/tallow-tui/dist/undo-stack.js +25 -0
- package/packages/tallow-tui/dist/undo-stack.js.map +1 -0
- package/packages/tallow-tui/dist/utils.d.ts +96 -0
- package/packages/tallow-tui/dist/utils.d.ts.map +1 -0
- package/packages/tallow-tui/dist/utils.js +843 -0
- package/packages/tallow-tui/dist/utils.js.map +1 -0
- package/packages/tallow-tui/package.json +24 -0
- package/packages/tallow-tui/src/__tests__/__snapshots__/render.test.ts.snap +121 -0
- package/packages/tallow-tui/src/__tests__/editor-border.test.ts +72 -0
- package/packages/tallow-tui/src/__tests__/editor-change-listener.test.ts +121 -0
- package/packages/tallow-tui/src/__tests__/editor-ghost-text.test.ts +112 -0
- package/packages/tallow-tui/src/__tests__/fuzzy.test.ts +91 -0
- package/packages/tallow-tui/src/__tests__/image-component.test.ts +113 -0
- package/packages/tallow-tui/src/__tests__/keys.test.ts +141 -0
- package/packages/tallow-tui/src/__tests__/render.test.ts +179 -0
- package/packages/tallow-tui/src/__tests__/stdin-buffer.test.ts +82 -0
- package/packages/tallow-tui/src/__tests__/terminal-image.test.ts +363 -0
- package/packages/tallow-tui/src/__tests__/tui-diff-regression.test.ts +454 -0
- package/packages/tallow-tui/src/__tests__/tui-render-scheduling.test.ts +256 -0
- package/packages/tallow-tui/src/__tests__/utils.test.ts +259 -0
- package/packages/tallow-tui/src/autocomplete.ts +716 -0
- package/packages/tallow-tui/src/border-styles.ts +60 -0
- package/packages/tallow-tui/src/components/bordered-box.ts +113 -0
- package/packages/tallow-tui/src/components/box.ts +137 -0
- package/packages/tallow-tui/src/components/cancellable-loader.ts +40 -0
- package/packages/tallow-tui/src/components/editor.ts +2143 -0
- package/packages/tallow-tui/src/components/image.ts +315 -0
- package/packages/tallow-tui/src/components/input.ts +522 -0
- package/packages/tallow-tui/src/components/loader.ts +187 -0
- package/packages/tallow-tui/src/components/markdown.ts +780 -0
- package/packages/tallow-tui/src/components/select-list.ts +197 -0
- package/packages/tallow-tui/src/components/settings-list.ts +264 -0
- package/packages/tallow-tui/src/components/spacer.ts +28 -0
- package/packages/tallow-tui/src/components/text.ts +113 -0
- package/packages/tallow-tui/src/components/truncated-text.ts +65 -0
- package/packages/tallow-tui/src/editor-component.ts +92 -0
- package/packages/tallow-tui/src/fuzzy.ts +133 -0
- package/packages/tallow-tui/src/index.ts +118 -0
- package/packages/tallow-tui/src/keybindings.ts +183 -0
- package/packages/tallow-tui/src/keys.ts +1189 -0
- package/packages/tallow-tui/src/kill-ring.ts +46 -0
- package/packages/tallow-tui/src/stdin-buffer.ts +386 -0
- package/packages/tallow-tui/src/terminal-image.ts +619 -0
- package/packages/tallow-tui/src/terminal.ts +350 -0
- package/packages/tallow-tui/src/test-utils/capability-env.ts +56 -0
- package/packages/tallow-tui/src/tui.ts +1336 -0
- package/packages/tallow-tui/src/undo-stack.ts +28 -0
- package/packages/tallow-tui/src/utils.ts +948 -0
- package/packages/tallow-tui/tsconfig.build.json +21 -0
- package/runtime/agent-runner.ts +20 -0
- package/runtime/atomic-write.ts +8 -0
- package/runtime/otel.ts +12 -0
- package/runtime/resolve-module.ts +23 -0
- package/runtime/runtime-path-provider.ts +12 -0
- package/runtime/runtime-provenance.ts +17 -0
- package/runtime/workspace-transition-relay.ts +21 -0
- package/runtime/workspace-transition.ts +29 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"whenToUse": "Use when the model should invoke slash commands through a tool.",
|
|
6
6
|
"capabilities": {
|
|
7
7
|
"tools": ["run_slash_command"],
|
|
8
|
-
"events": ["
|
|
8
|
+
"events": ["before_agent_start", "session_before_switch", "turn_end", "turn_start"]
|
|
9
9
|
},
|
|
10
10
|
"permissionSurface": {
|
|
11
11
|
"filesystem": "none",
|
|
@@ -9,15 +9,20 @@
|
|
|
9
9
|
* registration, with auto-generated tool schemas and full command handler access.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ContextUsage,
|
|
14
|
+
ExtensionAPI,
|
|
15
|
+
ExtensionContext,
|
|
16
|
+
TurnEndEvent,
|
|
17
|
+
} from "@mariozechner/pi-coding-agent";
|
|
13
18
|
import { Text } from "@mariozechner/pi-tui";
|
|
14
19
|
import { Type } from "@sinclair/typebox";
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
|
-
* Deferred compact request — set by the tool handler, consumed
|
|
18
|
-
* `
|
|
19
|
-
* `ctx.compact()` aborts the agent mid-tool-call,
|
|
20
|
-
* execution UI component. See plans 95
|
|
22
|
+
* Deferred compact request — set by the tool handler, consumed on the first
|
|
23
|
+
* safe assistant `turn_end` after the tool result. Deferring avoids the
|
|
24
|
+
* spinner-hang bug where `ctx.compact()` aborts the agent mid-tool-call,
|
|
25
|
+
* orphaning the tool execution UI component. See plans 95, 98, and 191.
|
|
21
26
|
*/
|
|
22
27
|
let pendingCompact: { customInstructions?: string } | null = null;
|
|
23
28
|
|
|
@@ -35,7 +40,7 @@ let resumingAfterCompact = false;
|
|
|
35
40
|
* `flushCompactionQueue` or user input already prompted the agent), or
|
|
36
41
|
* on session switch. See plan 159, bug 1.
|
|
37
42
|
*/
|
|
38
|
-
let continuationTimer:
|
|
43
|
+
let continuationTimer: SchedulerHandle | null = null;
|
|
39
44
|
|
|
40
45
|
/** Spinner frames for compact progress status updates. */
|
|
41
46
|
const COMPACT_PROGRESS_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -43,9 +48,39 @@ const COMPACT_PROGRESS_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦"
|
|
|
43
48
|
/** Interval cadence for compact progress status updates. */
|
|
44
49
|
const COMPACT_PROGRESS_INTERVAL_MS = 1000;
|
|
45
50
|
|
|
51
|
+
type SchedulerHandle = unknown;
|
|
52
|
+
|
|
53
|
+
/** Timer scheduler used by compact UI and continuation timers. */
|
|
54
|
+
export interface SlashCommandBridgeTimerScheduler {
|
|
55
|
+
readonly now: () => number;
|
|
56
|
+
readonly setInterval: (callback: () => void, intervalMs: number) => SchedulerHandle;
|
|
57
|
+
readonly clearInterval: (handle: SchedulerHandle | null) => void;
|
|
58
|
+
readonly setTimeout: (callback: () => void, delayMs: number) => SchedulerHandle;
|
|
59
|
+
readonly clearTimeout: (handle: SchedulerHandle | null) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Default runtime timer scheduler. */
|
|
63
|
+
const DEFAULT_TIMER_SCHEDULER: SlashCommandBridgeTimerScheduler = {
|
|
64
|
+
now: () => Date.now(),
|
|
65
|
+
setInterval: (callback, intervalMs) => setInterval(callback, intervalMs),
|
|
66
|
+
clearInterval: (handle) => {
|
|
67
|
+
if (handle) {
|
|
68
|
+
clearInterval(handle as ReturnType<typeof setInterval>);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
72
|
+
clearTimeout: (handle) => {
|
|
73
|
+
if (handle) {
|
|
74
|
+
clearTimeout(handle as ReturnType<typeof setTimeout>);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let timerScheduler: SlashCommandBridgeTimerScheduler = DEFAULT_TIMER_SCHEDULER;
|
|
80
|
+
|
|
46
81
|
/** Module-level heartbeat state for deferred compact UI updates. */
|
|
47
82
|
const compactProgressState: {
|
|
48
|
-
interval:
|
|
83
|
+
interval: SchedulerHandle | null;
|
|
49
84
|
spinnerIndex: number;
|
|
50
85
|
startedAt: number;
|
|
51
86
|
} = {
|
|
@@ -55,52 +90,114 @@ const compactProgressState: {
|
|
|
55
90
|
};
|
|
56
91
|
|
|
57
92
|
/**
|
|
58
|
-
* Starts compact progress heartbeat updates
|
|
93
|
+
* Starts compact progress heartbeat updates as an inline widget above the editor.
|
|
59
94
|
*
|
|
60
95
|
* This helper is idempotent: it always clears any previous heartbeat before
|
|
61
96
|
* starting a new one, preventing duplicate intervals after retries.
|
|
62
97
|
*
|
|
63
|
-
* @param ctx - Extension context used to
|
|
98
|
+
* @param ctx - Extension context used to render the compact progress widget
|
|
64
99
|
* @returns Nothing
|
|
65
100
|
*/
|
|
66
101
|
function startCompactProgress(ctx: ExtensionContext): void {
|
|
67
|
-
stopCompactProgress();
|
|
102
|
+
stopCompactProgress(ctx);
|
|
68
103
|
|
|
69
|
-
if (!ctx.ui?.
|
|
104
|
+
if (!ctx.ui?.setWidget) {
|
|
70
105
|
return;
|
|
71
106
|
}
|
|
72
107
|
|
|
73
|
-
compactProgressState.startedAt =
|
|
108
|
+
compactProgressState.startedAt = timerScheduler.now();
|
|
74
109
|
compactProgressState.spinnerIndex = 0;
|
|
75
110
|
|
|
76
|
-
const
|
|
77
|
-
const elapsedSeconds = Math.floor(
|
|
111
|
+
const renderWidget = () => {
|
|
112
|
+
const elapsedSeconds = Math.floor(
|
|
113
|
+
(timerScheduler.now() - compactProgressState.startedAt) / 1000
|
|
114
|
+
);
|
|
78
115
|
const frame =
|
|
79
116
|
COMPACT_PROGRESS_FRAMES[compactProgressState.spinnerIndex] ?? COMPACT_PROGRESS_FRAMES[0];
|
|
80
|
-
ctx.ui?.
|
|
117
|
+
ctx.ui?.setWidget?.("compact-progress", [
|
|
118
|
+
`🧹 ${frame} Compacting session · ${elapsedSeconds}s`,
|
|
119
|
+
]);
|
|
81
120
|
compactProgressState.spinnerIndex =
|
|
82
121
|
(compactProgressState.spinnerIndex + 1) % COMPACT_PROGRESS_FRAMES.length;
|
|
83
122
|
};
|
|
84
123
|
|
|
85
|
-
|
|
86
|
-
compactProgressState.interval = setInterval(
|
|
124
|
+
renderWidget();
|
|
125
|
+
compactProgressState.interval = timerScheduler.setInterval(
|
|
126
|
+
renderWidget,
|
|
127
|
+
COMPACT_PROGRESS_INTERVAL_MS
|
|
128
|
+
);
|
|
87
129
|
}
|
|
88
130
|
|
|
89
131
|
/**
|
|
90
|
-
* Stops compact progress heartbeat updates
|
|
132
|
+
* Stops compact progress heartbeat updates, clears the inline widget, and
|
|
133
|
+
* resets module-level state.
|
|
91
134
|
*
|
|
135
|
+
* @param ctx - Extension context used to clear the widget (optional for test/cleanup paths)
|
|
92
136
|
* @returns Nothing
|
|
93
137
|
*/
|
|
94
|
-
function stopCompactProgress(): void {
|
|
138
|
+
function stopCompactProgress(ctx?: ExtensionContext): void {
|
|
95
139
|
if (compactProgressState.interval) {
|
|
96
|
-
clearInterval(compactProgressState.interval);
|
|
140
|
+
timerScheduler.clearInterval(compactProgressState.interval);
|
|
97
141
|
compactProgressState.interval = null;
|
|
142
|
+
// Only clear the widget when a heartbeat was actually running.
|
|
143
|
+
// Avoids a spurious undefined update when startCompactProgress
|
|
144
|
+
// calls this as an idempotent reset before starting fresh.
|
|
145
|
+
ctx?.ui?.setWidget?.("compact-progress", undefined);
|
|
98
146
|
}
|
|
99
147
|
|
|
100
148
|
compactProgressState.startedAt = 0;
|
|
101
149
|
compactProgressState.spinnerIndex = 0;
|
|
102
150
|
}
|
|
103
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Cancels the pending continuation timer, if one exists.
|
|
154
|
+
*
|
|
155
|
+
* @returns Nothing
|
|
156
|
+
*/
|
|
157
|
+
function clearContinuationTimer(): void {
|
|
158
|
+
if (!continuationTimer) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
timerScheduler.clearTimeout(continuationTimer);
|
|
163
|
+
continuationTimer = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resets module-level compact state between runs or tests.
|
|
168
|
+
*
|
|
169
|
+
* @returns Nothing
|
|
170
|
+
*/
|
|
171
|
+
function clearCompactRuntimeState(): void {
|
|
172
|
+
pendingCompact = null;
|
|
173
|
+
resumingAfterCompact = false;
|
|
174
|
+
stopCompactProgress();
|
|
175
|
+
clearContinuationTimer();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Installs a deterministic timer scheduler for tests.
|
|
180
|
+
*
|
|
181
|
+
* @param scheduler - Test scheduler implementation
|
|
182
|
+
* @returns Nothing
|
|
183
|
+
*/
|
|
184
|
+
export function setSlashCommandBridgeSchedulerForTests(
|
|
185
|
+
scheduler: SlashCommandBridgeTimerScheduler
|
|
186
|
+
): void {
|
|
187
|
+
clearCompactRuntimeState();
|
|
188
|
+
timerScheduler = scheduler;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resets test scheduler/state overrides back to runtime defaults.
|
|
193
|
+
*
|
|
194
|
+
* @returns Nothing
|
|
195
|
+
*/
|
|
196
|
+
export function resetSlashCommandBridgeStateForTests(): void {
|
|
197
|
+
clearCompactRuntimeState();
|
|
198
|
+
timerScheduler = DEFAULT_TIMER_SCHEDULER;
|
|
199
|
+
}
|
|
200
|
+
|
|
104
201
|
/**
|
|
105
202
|
* Commands the model is allowed to invoke.
|
|
106
203
|
* Maps command name → whether it's executable from tool context.
|
|
@@ -177,6 +274,101 @@ function formatContextUsage(usage: KnownContextUsage): string {
|
|
|
177
274
|
return lines.join("\n");
|
|
178
275
|
}
|
|
179
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Returns true when the current turn_end is the first safe compaction boundary.
|
|
279
|
+
*
|
|
280
|
+
* The compact tool always finishes on a `toolUse` turn first. The model then
|
|
281
|
+
* gets one more assistant turn to finish its response after seeing the tool
|
|
282
|
+
* result. Consuming the request on `agent_end` is too late: `session.prompt()`
|
|
283
|
+
* returns before prior `agent_end` extension work fully drains, so a stale
|
|
284
|
+
* `agent_end` from the previous run can steal a newer compact request. The
|
|
285
|
+
* first assistant `turn_end` whose stop reason is not `toolUse` is the proven
|
|
286
|
+
* boundary for starting deferred compaction exactly once.
|
|
287
|
+
*
|
|
288
|
+
* @param event - Turn lifecycle event
|
|
289
|
+
* @returns True when deferred compaction should start now
|
|
290
|
+
*/
|
|
291
|
+
function shouldStartDeferredCompactOnTurnEnd(event: TurnEndEvent): boolean {
|
|
292
|
+
return event.message.role === "assistant" && event.message.stopReason !== "toolUse";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Starts the deferred compact flow and wires continuation/error cleanup.
|
|
297
|
+
*
|
|
298
|
+
* @param pi - Extension API used to enqueue the hidden continuation message
|
|
299
|
+
* @param ctx - Extension context with compact/UI capabilities
|
|
300
|
+
* @param options - Deferred compact request options
|
|
301
|
+
* @returns Nothing
|
|
302
|
+
*/
|
|
303
|
+
function startDeferredCompact(
|
|
304
|
+
pi: ExtensionAPI,
|
|
305
|
+
ctx: ExtensionContext,
|
|
306
|
+
options: { customInstructions?: string }
|
|
307
|
+
): void {
|
|
308
|
+
// Show explicit UI feedback while compaction runs. Without this,
|
|
309
|
+
// users only see the deferred tool message and no live progress signal.
|
|
310
|
+
ctx.ui?.setWorkingMessage?.("Compacting session…");
|
|
311
|
+
startCompactProgress(ctx);
|
|
312
|
+
|
|
313
|
+
ctx.compact({
|
|
314
|
+
customInstructions: options.customInstructions,
|
|
315
|
+
onComplete: () => {
|
|
316
|
+
stopCompactProgress(ctx);
|
|
317
|
+
|
|
318
|
+
// Transition from compaction indicators to resuming indicators.
|
|
319
|
+
// setWorkingMessage queues as pendingWorkingMessage (no loader
|
|
320
|
+
// exists after executeCompaction stops it). Applied automatically
|
|
321
|
+
// when agent_start creates the loader. The inline widget covers
|
|
322
|
+
// the brief gap before the loader appears.
|
|
323
|
+
resumingAfterCompact = true;
|
|
324
|
+
ctx.ui?.setWorkingMessage?.("Resuming task…");
|
|
325
|
+
ctx.ui?.setWidget?.("compact-progress", ["⏳ Resuming after compaction…"]);
|
|
326
|
+
|
|
327
|
+
// Always schedule continuation. Safety nets prevent duplicate prompts:
|
|
328
|
+
// 1. turn_start listener cancels if flushCompactionQueue already started a turn
|
|
329
|
+
// 2. isIdle() check at timer expiry skips if agent is streaming
|
|
330
|
+
// 3. sendCustomMessage queues as steering if agent started mid-delay
|
|
331
|
+
//
|
|
332
|
+
// Previously gated on hasCompactionQueuedMessages(), but that method
|
|
333
|
+
// checked both the compaction queue AND session steering — causing a
|
|
334
|
+
// false positive when steering messages were queued before compact.
|
|
335
|
+
// flushCompactionQueue only processes compactionQueuedMessages, so
|
|
336
|
+
// session steering messages were orphaned. See plan 160.
|
|
337
|
+
//
|
|
338
|
+
// 200ms gives session.prompt()'s async setup (API key resolution,
|
|
339
|
+
// compaction check) time to settle. The turn_start listener cancels
|
|
340
|
+
// this timer if a turn starts before it fires (defense-in-depth).
|
|
341
|
+
continuationTimer = timerScheduler.setTimeout(() => {
|
|
342
|
+
continuationTimer = null;
|
|
343
|
+
if (ctx.isIdle()) {
|
|
344
|
+
pi.sendMessage(
|
|
345
|
+
{
|
|
346
|
+
customType: "compact-continue",
|
|
347
|
+
content:
|
|
348
|
+
"Session compaction is complete. Continue with the task " +
|
|
349
|
+
"you were working on before compaction was triggered.",
|
|
350
|
+
display: false,
|
|
351
|
+
},
|
|
352
|
+
{ triggerTurn: true }
|
|
353
|
+
);
|
|
354
|
+
} else {
|
|
355
|
+
// User sent a message during compaction — their turn is
|
|
356
|
+
// handling things, clean up our indicators.
|
|
357
|
+
resumingAfterCompact = false;
|
|
358
|
+
ctx.ui?.setWidget?.("compact-progress", undefined);
|
|
359
|
+
ctx.ui?.setWorkingMessage?.();
|
|
360
|
+
}
|
|
361
|
+
}, 200);
|
|
362
|
+
},
|
|
363
|
+
onError: () => {
|
|
364
|
+
stopCompactProgress(ctx);
|
|
365
|
+
ctx.ui?.setWorkingMessage?.();
|
|
366
|
+
// Framework's executeCompaction handles error/cancel
|
|
367
|
+
// display. No continuation on failure — user decides.
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
180
372
|
/**
|
|
181
373
|
* Registers the slash-command-bridge tool and context injection.
|
|
182
374
|
*
|
|
@@ -299,8 +491,8 @@ WHEN NOT TO USE:
|
|
|
299
491
|
|
|
300
492
|
case "compact": {
|
|
301
493
|
// Don't call ctx.compact() here — it aborts the agent mid-tool-call,
|
|
302
|
-
// orphaning the tool execution spinner (plan 95/98). Defer to
|
|
303
|
-
//
|
|
494
|
+
// orphaning the tool execution spinner (plan 95/98). Defer to a
|
|
495
|
+
// proven turn_end boundary so the tool completes normally first.
|
|
304
496
|
pendingCompact = { customInstructions: undefined };
|
|
305
497
|
|
|
306
498
|
return {
|
|
@@ -350,93 +542,23 @@ WHEN NOT TO USE:
|
|
|
350
542
|
// ── Deferred compact ─────────────────────────────────────────
|
|
351
543
|
|
|
352
544
|
/**
|
|
353
|
-
* Fires compact
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* After compaction completes, checks whether the agent is idle. When the
|
|
358
|
-
* model triggered compaction (vs. the user typing during it), the
|
|
359
|
-
* framework's `flushCompactionQueue` finds no queued messages and the
|
|
360
|
-
* agent sits at the input prompt — even though the model promised to
|
|
361
|
-
* continue. We fix this by sending a hidden continuation message via
|
|
362
|
-
* `pi.sendMessage()` with `triggerTurn: true` to re-prompt the agent.
|
|
545
|
+
* Fires compact on the first safe assistant `turn_end` after the compact tool
|
|
546
|
+
* returns. The immediate `toolUse` turn is too early because the model has not
|
|
547
|
+
* finished its post-tool response yet, while `agent_end` is too late because a
|
|
548
|
+
* stale `agent_end` from the previous run can steal a newer pending request.
|
|
363
549
|
*
|
|
364
|
-
*
|
|
365
|
-
* async path to settle first, so we don't conflict with user-queued
|
|
366
|
-
* messages that already restarted the agent.
|
|
367
|
-
*
|
|
368
|
-
* @see Plan 98 — deferred compact to agent_end (introduced idle-after-compact)
|
|
550
|
+
* @see Plan 98 — deferred compact moved out of the tool handler
|
|
369
551
|
* @see Plan 157 — auto-continue after model-triggered compaction
|
|
552
|
+
* @see Plan 191 — stale prior agent_end could consume a later compact request
|
|
370
553
|
*/
|
|
371
|
-
pi.on("
|
|
372
|
-
if (!pendingCompact)
|
|
554
|
+
pi.on("turn_end", (event, ctx) => {
|
|
555
|
+
if (!pendingCompact || !shouldStartDeferredCompactOnTurnEnd(event)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
373
558
|
|
|
374
559
|
const options = pendingCompact;
|
|
375
560
|
pendingCompact = null;
|
|
376
|
-
|
|
377
|
-
// Show explicit UI feedback while compaction runs. Without this,
|
|
378
|
-
// users only see the deferred tool message and no live progress signal.
|
|
379
|
-
ctx.ui?.setWorkingMessage?.("Compacting session…");
|
|
380
|
-
startCompactProgress(ctx);
|
|
381
|
-
|
|
382
|
-
ctx.compact({
|
|
383
|
-
customInstructions: options.customInstructions,
|
|
384
|
-
onComplete: () => {
|
|
385
|
-
stopCompactProgress();
|
|
386
|
-
|
|
387
|
-
// Transition from compaction indicators to resuming indicators.
|
|
388
|
-
// setWorkingMessage queues as pendingWorkingMessage (no loader
|
|
389
|
-
// exists after executeCompaction stops it). Applied automatically
|
|
390
|
-
// when agent_start creates the loader. Footer status is visible
|
|
391
|
-
// immediately — covers the brief gap before the loader appears.
|
|
392
|
-
resumingAfterCompact = true;
|
|
393
|
-
ctx.ui?.setWorkingMessage?.("Resuming task…");
|
|
394
|
-
ctx.ui?.setStatus?.("compact", "⏳ resuming");
|
|
395
|
-
|
|
396
|
-
// Always schedule continuation. Safety nets prevent duplicate prompts:
|
|
397
|
-
// 1. turn_start listener cancels if flushCompactionQueue already started a turn
|
|
398
|
-
// 2. isIdle() check at timer expiry skips if agent is streaming
|
|
399
|
-
// 3. sendCustomMessage queues as steering if agent started mid-delay
|
|
400
|
-
//
|
|
401
|
-
// Previously gated on hasCompactionQueuedMessages(), but that method
|
|
402
|
-
// checked both the compaction queue AND session steering — causing a
|
|
403
|
-
// false positive when steering messages were queued before compact.
|
|
404
|
-
// flushCompactionQueue only processes compactionQueuedMessages, so
|
|
405
|
-
// session steering messages were orphaned. See plan 160.
|
|
406
|
-
//
|
|
407
|
-
// 200ms gives session.prompt()'s async setup (API key resolution,
|
|
408
|
-
// compaction check) time to settle. The turn_start listener cancels
|
|
409
|
-
// this timer if a turn starts before it fires (defense-in-depth).
|
|
410
|
-
continuationTimer = setTimeout(() => {
|
|
411
|
-
continuationTimer = null;
|
|
412
|
-
if (ctx.isIdle()) {
|
|
413
|
-
pi.sendMessage(
|
|
414
|
-
{
|
|
415
|
-
customType: "compact-continue",
|
|
416
|
-
content:
|
|
417
|
-
"Session compaction is complete. Continue with the task " +
|
|
418
|
-
"you were working on before compaction was triggered.",
|
|
419
|
-
display: false,
|
|
420
|
-
},
|
|
421
|
-
{ triggerTurn: true }
|
|
422
|
-
);
|
|
423
|
-
} else {
|
|
424
|
-
// User sent a message during compaction — their turn is
|
|
425
|
-
// handling things, clean up our indicators.
|
|
426
|
-
resumingAfterCompact = false;
|
|
427
|
-
ctx.ui?.setStatus?.("compact", undefined);
|
|
428
|
-
ctx.ui?.setWorkingMessage?.();
|
|
429
|
-
}
|
|
430
|
-
}, 200);
|
|
431
|
-
},
|
|
432
|
-
onError: () => {
|
|
433
|
-
stopCompactProgress();
|
|
434
|
-
ctx.ui?.setWorkingMessage?.();
|
|
435
|
-
ctx.ui?.setStatus?.("compact", undefined);
|
|
436
|
-
// Framework's executeCompaction handles error/cancel
|
|
437
|
-
// display. No continuation on failure — user decides.
|
|
438
|
-
},
|
|
439
|
-
});
|
|
561
|
+
startDeferredCompact(pi, ctx, options);
|
|
440
562
|
});
|
|
441
563
|
|
|
442
564
|
/**
|
|
@@ -450,13 +572,10 @@ WHEN NOT TO USE:
|
|
|
450
572
|
* now active and showing the pending working message ("Resuming task…").
|
|
451
573
|
*/
|
|
452
574
|
pi.on("turn_start", (_event, ctx) => {
|
|
453
|
-
|
|
454
|
-
clearTimeout(continuationTimer);
|
|
455
|
-
continuationTimer = null;
|
|
456
|
-
}
|
|
575
|
+
clearContinuationTimer();
|
|
457
576
|
if (!resumingAfterCompact) return;
|
|
458
577
|
resumingAfterCompact = false;
|
|
459
|
-
ctx.ui?.
|
|
578
|
+
ctx.ui?.setWidget?.("compact-progress", undefined);
|
|
460
579
|
});
|
|
461
580
|
|
|
462
581
|
/**
|
|
@@ -464,13 +583,8 @@ WHEN NOT TO USE:
|
|
|
464
583
|
* session switches before the turn ends.
|
|
465
584
|
*/
|
|
466
585
|
pi.on("session_before_switch", (_event, ctx) => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (continuationTimer) {
|
|
471
|
-
clearTimeout(continuationTimer);
|
|
472
|
-
continuationTimer = null;
|
|
473
|
-
}
|
|
474
|
-
ctx.ui?.setStatus?.("compact", undefined);
|
|
586
|
+
clearCompactRuntimeState();
|
|
587
|
+
ctx.ui?.setWidget?.("compact-progress", undefined);
|
|
588
|
+
ctx.ui?.setWorkingMessage?.();
|
|
475
589
|
});
|
|
476
590
|
}
|
|
@@ -143,11 +143,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
143
143
|
publishSubagentSnapshot(pi.events);
|
|
144
144
|
|
|
145
145
|
// Request telemetry handle for subprocess trace context injection.
|
|
146
|
-
const { TELEMETRY_API_CHANNELS } = await import("../../
|
|
146
|
+
const { TELEMETRY_API_CHANNELS } = await import("../../runtime/otel.js");
|
|
147
147
|
const onTelemetryApi = (payload: unknown): void => {
|
|
148
148
|
if (payload && typeof payload === "object" && "handle" in payload) {
|
|
149
149
|
setTelemetryHandle(
|
|
150
|
-
(payload as { handle: import("../../
|
|
150
|
+
(payload as { handle: import("../../runtime/otel.js").TelemetryHandle }).handle
|
|
151
151
|
);
|
|
152
152
|
}
|
|
153
153
|
};
|
|
@@ -12,12 +12,11 @@ import * as path from "node:path";
|
|
|
12
12
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
13
13
|
import type { Message } from "@mariozechner/pi-ai";
|
|
14
14
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
15
|
-
import { DEFAULT_AGENT_RUNNER_ENV, spawnWithResolvedAgentRunner } from "../../src/agent-runner.js";
|
|
16
15
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "../../
|
|
16
|
+
DEFAULT_AGENT_RUNNER_ENV,
|
|
17
|
+
spawnWithResolvedAgentRunner,
|
|
18
|
+
} from "../../runtime/agent-runner.js";
|
|
19
|
+
import { injectTraceContextToEnv, type TelemetryHandle } from "../../runtime/otel.js";
|
|
21
20
|
import { extractPreview, isInlineResultsEnabled } from "../_shared/inline-preview.js";
|
|
22
21
|
import {
|
|
23
22
|
emitWorktreeLifecycleEvent,
|
|
@@ -11,6 +11,7 @@ import { Key, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
|
11
11
|
import { Type } from "@sinclair/typebox";
|
|
12
12
|
import { getIcon, getSpinner } from "../../_icons/index.js";
|
|
13
13
|
import {
|
|
14
|
+
emitInteropEvent,
|
|
14
15
|
INTEROP_EVENT_NAMES,
|
|
15
16
|
onInteropEvent,
|
|
16
17
|
requestInteropState,
|
|
@@ -106,6 +107,7 @@ export function registerTasksExtension(
|
|
|
106
107
|
let backgroundTasks: BgTaskView[] = [];
|
|
107
108
|
let activeTeams: TeamWidgetView[] = [];
|
|
108
109
|
let teamDashboardActive = false;
|
|
110
|
+
let lastBackgroundTaskPresenterState: boolean | null = null;
|
|
109
111
|
|
|
110
112
|
if (tasksAnimationInterval) clearInterval(tasksAnimationInterval);
|
|
111
113
|
tasksAnimationInterval = undefined;
|
|
@@ -573,10 +575,40 @@ export function registerTasksExtension(
|
|
|
573
575
|
);
|
|
574
576
|
}
|
|
575
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Determine whether the tasks extension currently owns background-task
|
|
580
|
+
* widget presentation.
|
|
581
|
+
*
|
|
582
|
+
* The tasks widget can safely suppress the standalone background-task-tool
|
|
583
|
+
* widget whenever this main dashboard is visible and the team dashboard is
|
|
584
|
+
* not active.
|
|
585
|
+
*
|
|
586
|
+
* @returns True when the tasks widget should be the canonical presenter
|
|
587
|
+
*/
|
|
588
|
+
function shouldPresentBackgroundTasks(): boolean {
|
|
589
|
+
return !isSubagent && state.visible && !teamDashboardActive;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Publish background-task presenter ownership for background-task-tool.
|
|
594
|
+
*
|
|
595
|
+
* @returns void
|
|
596
|
+
*/
|
|
597
|
+
function publishBackgroundTaskPresenterState(): void {
|
|
598
|
+
const nextState = shouldPresentBackgroundTasks();
|
|
599
|
+
if (lastBackgroundTaskPresenterState === nextState) return;
|
|
600
|
+
lastBackgroundTaskPresenterState = nextState;
|
|
601
|
+
emitInteropEvent(pi.events, INTEROP_EVENT_NAMES.backgroundTasksPresenterState, {
|
|
602
|
+
active: nextState,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
576
606
|
function updateWidget(ctx: ExtensionContext): void {
|
|
577
607
|
// Subagents have no UI — skip all widget rendering
|
|
578
608
|
if (isSubagent) return;
|
|
579
609
|
|
|
610
|
+
publishBackgroundTaskPresenterState();
|
|
611
|
+
|
|
580
612
|
// If every task is completed and the 2s completion window has passed,
|
|
581
613
|
// clear the list. This covers extension reloads where the original
|
|
582
614
|
// setTimeout callback was lost before it could run.
|
|
@@ -1891,6 +1923,7 @@ Before calling manage_tasks complete/update, call manage_tasks list first so ind
|
|
|
1891
1923
|
backgroundTasks = [];
|
|
1892
1924
|
activeTeams = [];
|
|
1893
1925
|
teamDashboardActive = false;
|
|
1926
|
+
lastBackgroundTaskPresenterState = null;
|
|
1894
1927
|
lastBgCount = 0;
|
|
1895
1928
|
lastBgTaskCount = 0;
|
|
1896
1929
|
|
|
@@ -1990,11 +2023,15 @@ Before calling manage_tasks complete/update, call manage_tasks list first so ind
|
|
|
1990
2023
|
updateWidget(ctx);
|
|
1991
2024
|
}
|
|
1992
2025
|
);
|
|
2026
|
+
const unsubStateRequest = onInteropEvent(pi.events, INTEROP_EVENT_NAMES.stateRequest, () => {
|
|
2027
|
+
publishBackgroundTaskPresenterState();
|
|
2028
|
+
});
|
|
1993
2029
|
interopEventsCleanup = () => {
|
|
1994
2030
|
unsubSubagents();
|
|
1995
2031
|
unsubBackgroundTasks();
|
|
1996
2032
|
unsubTeams();
|
|
1997
2033
|
unsubDashboardState();
|
|
2034
|
+
unsubStateRequest();
|
|
1998
2035
|
};
|
|
1999
2036
|
|
|
2000
2037
|
legacyInteropBridgeCleanup?.();
|
|
@@ -2098,6 +2135,10 @@ Before calling manage_tasks complete/update, call manage_tasks list first so ind
|
|
|
2098
2135
|
|
|
2099
2136
|
// Cleanup on session end
|
|
2100
2137
|
pi.on("session_shutdown", async () => {
|
|
2138
|
+
emitInteropEvent(pi.events, INTEROP_EVENT_NAMES.backgroundTasksPresenterState, {
|
|
2139
|
+
active: false,
|
|
2140
|
+
});
|
|
2141
|
+
lastBackgroundTaskPresenterState = false;
|
|
2101
2142
|
if (tasksAnimationInterval) {
|
|
2102
2143
|
clearInterval(tasksAnimationInterval);
|
|
2103
2144
|
tasksAnimationInterval = undefined;
|