@pellux/goodvibes-agent 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +7 -7
- package/dist/package/main.js +7424 -12241
- package/docs/README.md +2 -2
- package/docs/channels-remote-and-api.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/release-and-publishing.md +1 -1
- package/docs/tools-and-commands.md +5 -5
- package/package.json +4 -2
- package/release/performance-snapshot.json +2 -2
- package/release/release-notes.md +11 -7
- package/release/release-readiness.json +6 -6
- package/src/agent/behavior-discovery-summary.ts +4 -18
- package/src/agent/calendar-registry.ts +322 -0
- package/src/agent/competitive-feature-inventory.ts +268 -130
- package/src/agent/document-registry.ts +5 -1
- package/src/agent/email/email-service.ts +350 -0
- package/src/agent/email/imap-client.ts +596 -0
- package/src/agent/email/smtp-client.ts +453 -0
- package/src/agent/ics-calendar.ts +662 -0
- package/src/agent/ics-timezone.ts +172 -0
- package/src/agent/markdown-frontmatter.ts +31 -0
- package/src/agent/memory-safety.ts +1 -1
- package/src/agent/note-registry.ts +3 -9
- package/src/agent/persona-discovery.ts +3 -15
- package/src/agent/persona-registry.ts +1 -10
- package/src/agent/research-source-registry.ts +5 -1
- package/src/agent/routine-discovery.ts +3 -15
- package/src/agent/runtime-profile.ts +3 -0
- package/src/agent/skill-discovery.ts +3 -15
- package/src/agent/skill-draft-proposer.ts +203 -0
- package/src/agent/skill-draft-runner.ts +201 -0
- package/src/agent/skill-registry.ts +36 -1
- package/src/agent/skill-standard.ts +99 -0
- package/src/agent/vibe-file.ts +7 -22
- package/src/cli/completion.ts +1 -1
- package/src/cli/config-overrides.ts +10 -1
- package/src/cli/redaction.ts +17 -5
- package/src/config/provider-model.ts +2 -1
- package/src/config/secret-config.ts +13 -6
- package/src/core/activity-feed.ts +97 -0
- package/src/core/away-digest.ts +161 -0
- package/src/core/conversation-rendering.ts +7 -3
- package/src/core/conversation.ts +22 -15
- package/src/core/hardware-profile.ts +362 -0
- package/src/core/last-seen-store.ts +78 -0
- package/src/core/plain-language.ts +52 -0
- package/src/core/setup-incomplete-hint.ts +90 -0
- package/src/core/system-message-router.ts +38 -87
- package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
- package/src/input/agent-workspace-basic-command-editors.ts +39 -141
- package/src/input/agent-workspace-categories.ts +40 -125
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-host-category.ts +0 -5
- package/src/input/agent-workspace-local-editor-submission.ts +3 -1
- package/src/input/agent-workspace-navigation.ts +10 -3
- package/src/input/agent-workspace-onboarding-actions.ts +132 -0
- package/src/input/agent-workspace-onboarding-categories.ts +29 -26
- package/src/input/agent-workspace-onboarding-finish.ts +11 -4
- package/src/input/agent-workspace-onboarding-state.ts +111 -0
- package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
- package/src/input/agent-workspace-profile-editors.ts +155 -0
- package/src/input/agent-workspace-subscription-editor.ts +7 -0
- package/src/input/agent-workspace-types.ts +5 -1
- package/src/input/agent-workspace.ts +65 -39
- package/src/input/command-registry.ts +18 -5
- package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
- package/src/input/commands/agent-skills-runtime.ts +60 -3
- package/src/input/commands/calendar-runtime.ts +209 -0
- package/src/input/commands/channels-runtime.ts +1 -0
- package/src/input/commands/command-error.ts +9 -0
- package/src/input/commands/compat-runtime.ts +1 -0
- package/src/input/commands/config.ts +1 -0
- package/src/input/commands/conversation-runtime.ts +1 -1
- package/src/input/commands/delegation-runtime.ts +5 -6
- package/src/input/commands/email-runtime.ts +387 -0
- package/src/input/commands/experience-runtime.ts +17 -4
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +6 -1
- package/src/input/commands/knowledge-format.ts +73 -0
- package/src/input/commands/knowledge.ts +9 -74
- package/src/input/commands/local-provider-runtime.ts +2 -1
- package/src/input/commands/local-runtime.ts +10 -4
- package/src/input/commands/mcp-runtime.ts +7 -0
- package/src/input/commands/notify-runtime.ts +17 -1
- package/src/input/commands/onboarding-runtime.ts +1 -0
- package/src/input/commands/operator-actions-runtime.ts +1 -0
- package/src/input/commands/operator-runtime.ts +3 -0
- package/src/input/commands/personas-runtime.ts +1 -0
- package/src/input/commands/platform-access-runtime.ts +40 -17
- package/src/input/commands/product-runtime.ts +6 -1
- package/src/input/commands/provider-accounts-runtime.ts +3 -2
- package/src/input/commands/qrcode-runtime.ts +1 -0
- package/src/input/commands/routines-runtime.ts +1 -0
- package/src/input/commands/runtime-services.ts +0 -13
- package/src/input/commands/security-runtime.ts +1 -0
- package/src/input/commands/session-content.ts +1 -0
- package/src/input/commands/shell-core.ts +5 -2
- package/src/input/commands/subscription-runtime.ts +33 -9
- package/src/input/commands/support-bundle-runtime.ts +8 -1
- package/src/input/commands/tasks-runtime.ts +1 -0
- package/src/input/commands/tts-runtime.ts +1 -0
- package/src/input/commands/vibe-runtime.ts +1 -0
- package/src/input/commands.ts +4 -0
- package/src/input/feed-context-factory.ts +3 -12
- package/src/input/handler-command-route.ts +7 -60
- package/src/input/handler-feed-routes.ts +0 -194
- package/src/input/handler-feed.ts +2 -54
- package/src/input/handler-interactions.ts +0 -2
- package/src/input/handler-modal-stack.ts +0 -9
- package/src/input/handler-picker-routes.ts +24 -1
- package/src/input/handler-shortcuts.ts +11 -40
- package/src/input/handler-ui-state.ts +13 -0
- package/src/input/handler.ts +8 -35
- package/src/input/keybindings.ts +40 -17
- package/src/input/model-picker-local-fit.ts +130 -0
- package/src/input/submission-router.ts +0 -5
- package/src/main.ts +111 -89
- package/src/renderer/activity-sidebar.ts +186 -0
- package/src/renderer/agent-workspace-context-lines.ts +5 -48
- package/src/renderer/agent-workspace-style.ts +1 -1
- package/src/renderer/agent-workspace.ts +14 -2
- package/src/renderer/compositor.ts +37 -125
- package/src/renderer/conversation-overlays.ts +3 -3
- package/src/renderer/help-overlay.ts +7 -5
- package/src/renderer/model-workspace.ts +101 -86
- package/src/{panels → renderer}/polish.ts +3 -3
- package/src/renderer/shell-surface.ts +4 -5
- package/src/renderer/status-token.ts +31 -11
- package/src/renderer/tab-strip.ts +1 -1
- package/src/renderer/tool-call.ts +7 -8
- package/src/renderer/tool-labels.ts +92 -0
- package/src/renderer/ui-factory.ts +48 -96
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +6 -15
- package/src/runtime/bootstrap-core.ts +34 -13
- package/src/runtime/bootstrap-hook-bridge.ts +3 -1
- package/src/runtime/bootstrap-shell.ts +3 -50
- package/src/runtime/bootstrap.ts +3 -4
- package/src/runtime/context.ts +1 -1
- package/src/runtime/diagnostics/panels/index.ts +0 -1
- package/src/runtime/diagnostics/panels/policy.ts +1 -1
- package/src/runtime/execution-ledger.ts +16 -1
- package/src/runtime/index.ts +6 -1
- package/src/runtime/onboarding/index.ts +1 -0
- package/src/runtime/onboarding/onboarding-state.ts +200 -0
- package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
- package/src/runtime/services.ts +16 -4
- package/src/runtime/terminal-output-guard.ts +7 -0
- package/src/runtime/tool-permission-safety.ts +1 -1
- package/src/runtime/ui/model-picker/data-provider.ts +1 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
- package/src/runtime/ui/model-picker/types.ts +2 -2
- package/src/runtime/ui/provider-health/data-provider.ts +3 -3
- package/src/runtime/ui/provider-health/types.ts +2 -2
- package/src/runtime/ui-services.ts +0 -2
- package/src/shell/agent-workspace-fullscreen.ts +0 -1
- package/src/shell/autonomy-surfacing.ts +272 -0
- package/src/shell/session-continuity-hints.ts +0 -6
- package/src/shell/startup-wiring.ts +221 -0
- package/src/shell/ui-openers.ts +18 -29
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +4 -4
- package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
- package/src/tools/agent-harness-command-runner.ts +6 -1
- package/src/tools/agent-harness-document-ops.ts +26 -53
- package/src/tools/agent-harness-execution-history.ts +1 -13
- package/src/tools/agent-harness-execution-posture.ts +0 -15
- package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
- package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
- package/src/tools/agent-harness-metadata.ts +16 -0
- package/src/tools/agent-harness-mode-catalog.ts +2 -9
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
- package/src/tools/agent-harness-tool-schema.ts +13 -14
- package/src/tools/agent-harness-tool.ts +27 -23
- package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
- package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
- package/src/tools/agent-workspace-tool.ts +2 -33
- package/src/utils/terminal-width.ts +9 -3
- package/src/version.ts +1 -1
- package/src/input/agent-workspace-panel-route.ts +0 -44
- package/src/input/panel-integration-actions.ts +0 -26
- package/src/panels/approval-panel.ts +0 -149
- package/src/panels/automation-control-panel.ts +0 -212
- package/src/panels/base-panel.ts +0 -254
- package/src/panels/builtin/agent.ts +0 -58
- package/src/panels/builtin/knowledge.ts +0 -26
- package/src/panels/builtin/operations.ts +0 -121
- package/src/panels/builtin/session.ts +0 -138
- package/src/panels/builtin/shared.ts +0 -275
- package/src/panels/builtin/usage.ts +0 -21
- package/src/panels/builtin-panels.ts +0 -23
- package/src/panels/confirm-state.ts +0 -61
- package/src/panels/context-visualizer-panel.ts +0 -204
- package/src/panels/cost-tracker-panel.ts +0 -444
- package/src/panels/docs-panel.ts +0 -285
- package/src/panels/index.ts +0 -25
- package/src/panels/knowledge-panel.ts +0 -417
- package/src/panels/memory-panel.ts +0 -226
- package/src/panels/panel-list-panel.ts +0 -464
- package/src/panels/panel-manager.ts +0 -570
- package/src/panels/provider-accounts-panel.ts +0 -233
- package/src/panels/provider-health-domains.ts +0 -208
- package/src/panels/provider-health-panel.ts +0 -720
- package/src/panels/provider-health-tracker.ts +0 -115
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/qr-panel.ts +0 -207
- package/src/panels/schedule-panel.ts +0 -321
- package/src/panels/scrollable-list-panel.ts +0 -491
- package/src/panels/search-focus.ts +0 -32
- package/src/panels/security-panel.ts +0 -295
- package/src/panels/session-browser-panel.ts +0 -395
- package/src/panels/session-maintenance.ts +0 -125
- package/src/panels/subscription-panel.ts +0 -263
- package/src/panels/system-messages-panel.ts +0 -230
- package/src/panels/tasks-panel.ts +0 -344
- package/src/panels/thinking-panel.ts +0 -304
- package/src/panels/token-budget-panel.ts +0 -475
- package/src/panels/tool-inspector-panel.ts +0 -436
- package/src/panels/types.ts +0 -54
- package/src/renderer/panel-composite.ts +0 -158
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/renderer/panel-workspace-bar.ts +0 -42
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/tools/agent-harness-panel-metadata.ts +0 -211
- /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
|
-
import type { RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
|
|
5
|
-
import type { UiReadModel, UiTasksSnapshot } from '../runtime/ui-read-models.ts';
|
|
6
|
-
import {
|
|
7
|
-
buildDetailBlock,
|
|
8
|
-
buildEmptyState,
|
|
9
|
-
buildGuidanceLine,
|
|
10
|
-
buildPanelListRow,
|
|
11
|
-
buildPanelLine,
|
|
12
|
-
buildSummaryBlock,
|
|
13
|
-
buildPanelWorkspace,
|
|
14
|
-
DEFAULT_PANEL_PALETTE,
|
|
15
|
-
} from './polish.ts';
|
|
16
|
-
|
|
17
|
-
const C = {
|
|
18
|
-
...DEFAULT_PANEL_PALETTE,
|
|
19
|
-
header: '#94a3b8',
|
|
20
|
-
headerBg: '#1e293b',
|
|
21
|
-
queued: '#38bdf8',
|
|
22
|
-
running: '#22c55e',
|
|
23
|
-
blocked: '#f59e0b',
|
|
24
|
-
failed: '#ef4444',
|
|
25
|
-
completed: '#a78bfa',
|
|
26
|
-
selectBg: '#0f172a',
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
const STATUS_ORDER: TaskLifecycleState[] = ['queued', 'running', 'blocked', 'failed', 'completed'];
|
|
30
|
-
|
|
31
|
-
function formatWhen(value?: number): string {
|
|
32
|
-
return value ? new Date(value).toLocaleString() : 'n/a';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function formatDuration(startedAt?: number, endedAt?: number): string {
|
|
36
|
-
if (!startedAt) return 'n/a';
|
|
37
|
-
const end = endedAt ?? Date.now();
|
|
38
|
-
const ms = Math.max(0, end - startedAt);
|
|
39
|
-
if (ms < 1_000) return `${ms}ms`;
|
|
40
|
-
if (ms < 60_000) return `${(ms / 1_000).toFixed(ms < 10_000 ? 1 : 0)}s`;
|
|
41
|
-
const mins = Math.floor(ms / 60_000);
|
|
42
|
-
const secs = Math.floor((ms % 60_000) / 1_000);
|
|
43
|
-
return `${mins}m ${secs}s`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function kindLabel(kind: RuntimeTask['kind']): string {
|
|
47
|
-
switch (kind) {
|
|
48
|
-
case 'exec': return 'exec';
|
|
49
|
-
case 'agent': return 'task';
|
|
50
|
-
case 'acp': return 'delegate';
|
|
51
|
-
case 'scheduler': return 'scheduler';
|
|
52
|
-
case 'daemon': return 'host';
|
|
53
|
-
case 'mcp': return 'mcp';
|
|
54
|
-
case 'plugin': return 'plugin';
|
|
55
|
-
case 'integration': return 'integration';
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function descriptorOriginLabel(source: string | undefined): string {
|
|
60
|
-
const normalized = source?.trim();
|
|
61
|
-
if (!normalized || normalized === 'builtin/runtime') return 'runtime';
|
|
62
|
-
if (normalized === 'daemon' || normalized === 'daemon-runtime') return 'host';
|
|
63
|
-
return normalized.replace(/[-_/]+/g, ' ');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function statusColor(status: TaskLifecycleState): string {
|
|
67
|
-
switch (status) {
|
|
68
|
-
case 'queued':
|
|
69
|
-
return C.queued;
|
|
70
|
-
case 'running':
|
|
71
|
-
return C.running;
|
|
72
|
-
case 'blocked':
|
|
73
|
-
return C.blocked;
|
|
74
|
-
case 'failed':
|
|
75
|
-
return C.failed;
|
|
76
|
-
case 'completed':
|
|
77
|
-
return C.completed;
|
|
78
|
-
case 'cancelled':
|
|
79
|
-
return C.dim;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function sortTasks(tasks: RuntimeTask[]): RuntimeTask[] {
|
|
84
|
-
const order = new Map(STATUS_ORDER.map((status, index) => [status, index] as const));
|
|
85
|
-
return [...tasks].sort((a, b) => {
|
|
86
|
-
const statusDelta = (order.get(a.status) ?? 99) - (order.get(b.status) ?? 99);
|
|
87
|
-
if (statusDelta !== 0) return statusDelta;
|
|
88
|
-
const aTime = a.startedAt ?? a.queuedAt;
|
|
89
|
-
const bTime = b.startedAt ?? b.queuedAt;
|
|
90
|
-
return bTime - aTime || a.title.localeCompare(b.title);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function safeJson(value: unknown): string {
|
|
95
|
-
try {
|
|
96
|
-
return JSON.stringify(value);
|
|
97
|
-
} catch {
|
|
98
|
-
return String(value);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
interface TaskDescriptorMeta {
|
|
103
|
-
readonly mode?: string;
|
|
104
|
-
readonly template?: string;
|
|
105
|
-
readonly reviewMode?: string;
|
|
106
|
-
readonly executionProtocol?: string;
|
|
107
|
-
readonly source?: string;
|
|
108
|
-
readonly family?: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function parseTaskDescriptor(description: string): TaskDescriptorMeta | null {
|
|
112
|
-
try {
|
|
113
|
-
const parsed = JSON.parse(description) as TaskDescriptorMeta;
|
|
114
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
115
|
-
return parsed;
|
|
116
|
-
} catch {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
122
|
-
private readonly readModel?: UiReadModel<UiTasksSnapshot>;
|
|
123
|
-
private readonly unsubscribers: readonly (() => void)[];
|
|
124
|
-
|
|
125
|
-
public constructor(readModel: UiReadModel<UiTasksSnapshot> | undefined) {
|
|
126
|
-
super('tasks', 'Tasks', 'J', 'monitoring');
|
|
127
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
128
|
-
this.readModel = readModel;
|
|
129
|
-
this.unsubscribers = [
|
|
130
|
-
readModel?.subscribe(() => this.markDirty()),
|
|
131
|
-
].filter((unsubscribe): unsubscribe is () => void => Boolean(unsubscribe));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public override onActivate(): void {
|
|
135
|
-
super.onActivate();
|
|
136
|
-
this.selectedIndex = 0;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public override onDestroy(): void {
|
|
140
|
-
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
protected override getPalette() { return C; }
|
|
144
|
-
protected override getEmptyStateMessage() { return ' No tasks recorded yet.'; }
|
|
145
|
-
protected override getEmptyStateActions() {
|
|
146
|
-
return [
|
|
147
|
-
{ command: '/workplan', summary: 'track visible Agent work in the workspace' },
|
|
148
|
-
{ command: '/delegate <task>', summary: 'send explicit build/fix/review work to GoodVibes TUI' },
|
|
149
|
-
];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
protected getItems(): readonly RuntimeTask[] {
|
|
153
|
-
if (!this.readModel) return [];
|
|
154
|
-
return sortTasks([...this.readModel.getSnapshot().tasks]);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
protected renderItem(task: RuntimeTask, index: number, selected: boolean, width: number): Line {
|
|
158
|
-
return buildPanelListRow(width, [
|
|
159
|
-
{ text: task.status.padEnd(10), fg: statusColor(task.status) },
|
|
160
|
-
{ text: ` ${kindLabel(task.kind).padEnd(12)}`, fg: C.value },
|
|
161
|
-
{ text: ` ${task.id.slice(0, 8)} `, fg: C.dim },
|
|
162
|
-
{ text: task.title.slice(0, Math.max(0, width - 37)), fg: C.value },
|
|
163
|
-
], C, { selected });
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
public handleInput(key: string): boolean {
|
|
167
|
-
if (key === 'home') {
|
|
168
|
-
this.selectedIndex = 0;
|
|
169
|
-
this.markDirty();
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
if (key === 'end') {
|
|
173
|
-
const tasks = this.getItems();
|
|
174
|
-
this.selectedIndex = Math.max(0, tasks.length - 1);
|
|
175
|
-
this.markDirty();
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
return super.handleInput(key);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
public render(width: number, height: number): Line[] {
|
|
182
|
-
this.clampSelection();
|
|
183
|
-
const intro = 'Connected-host task lifecycle, ownership, retries, and result/error details for operator visibility.';
|
|
184
|
-
const footerLines = [buildPanelLine(width, [[' Up/Down move Home/End jump', C.dim]])];
|
|
185
|
-
|
|
186
|
-
if (!this.readModel) {
|
|
187
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
188
|
-
title: 'Task Control Room',
|
|
189
|
-
intro,
|
|
190
|
-
sections: [{
|
|
191
|
-
lines: buildEmptyState(
|
|
192
|
-
width,
|
|
193
|
-
' Connected-host task state is unavailable.',
|
|
194
|
-
'This operator view is read-only. Use /tasks list for compact transcript output; task execution stays unchanged.',
|
|
195
|
-
[{ command: '/tasks', summary: 'review connected-host tasks without changing execution' }],
|
|
196
|
-
C,
|
|
197
|
-
),
|
|
198
|
-
}],
|
|
199
|
-
palette: C,
|
|
200
|
-
});
|
|
201
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
202
|
-
return workspace;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const tasks = this.getItems();
|
|
206
|
-
const counts = STATUS_ORDER.map((status) => ({
|
|
207
|
-
status,
|
|
208
|
-
count: tasks.filter((task) => task.status === status).length,
|
|
209
|
-
}));
|
|
210
|
-
const blockedCount = counts.find((entry) => entry.status === 'blocked')?.count ?? 0;
|
|
211
|
-
const failedCount = counts.find((entry) => entry.status === 'failed')?.count ?? 0;
|
|
212
|
-
const runningCount = counts.find((entry) => entry.status === 'running')?.count ?? 0;
|
|
213
|
-
const queuedCount = counts.find((entry) => entry.status === 'queued')?.count ?? 0;
|
|
214
|
-
const completedCount = counts.find((entry) => entry.status === 'completed')?.count ?? 0;
|
|
215
|
-
|
|
216
|
-
const postureLines: Line[] = [
|
|
217
|
-
buildPanelLine(width, [
|
|
218
|
-
[' queued ', C.label],
|
|
219
|
-
[String(queuedCount), queuedCount > 0 ? C.queued : C.dim],
|
|
220
|
-
[' running ', C.label],
|
|
221
|
-
[String(runningCount), runningCount > 0 ? C.running : C.dim],
|
|
222
|
-
[' blocked ', C.label],
|
|
223
|
-
[String(blockedCount), blockedCount > 0 ? C.blocked : C.dim],
|
|
224
|
-
[' failed ', C.label],
|
|
225
|
-
[String(failedCount), failedCount > 0 ? C.failed : C.dim],
|
|
226
|
-
[' completed ', C.label],
|
|
227
|
-
[String(completedCount), completedCount > 0 ? C.completed : C.dim],
|
|
228
|
-
]),
|
|
229
|
-
];
|
|
230
|
-
|
|
231
|
-
const selected = tasks[this.selectedIndex];
|
|
232
|
-
if (selected) {
|
|
233
|
-
postureLines.push(buildPanelLine(width, [
|
|
234
|
-
[' selected ', C.label],
|
|
235
|
-
[selected.id, C.info],
|
|
236
|
-
[' status ', C.label],
|
|
237
|
-
[selected.status, statusColor(selected.status)],
|
|
238
|
-
[' kind ', C.label],
|
|
239
|
-
[kindLabel(selected.kind), C.value],
|
|
240
|
-
[' owner ', C.label],
|
|
241
|
-
[selected.owner.slice(0, Math.max(0, width - 46)), C.dim],
|
|
242
|
-
]));
|
|
243
|
-
}
|
|
244
|
-
postureLines.push(
|
|
245
|
-
buildGuidanceLine(width, '/tasks', 'review connected-host task posture without changing execution', C),
|
|
246
|
-
buildGuidanceLine(width, '/delegate <task>', 'delegate explicit build/fix/review work to GoodVibes TUI when code execution is required', C),
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const detailRows: Line[] = [];
|
|
250
|
-
if (selected) {
|
|
251
|
-
const descriptor = selected.description ? parseTaskDescriptor(selected.description) : null;
|
|
252
|
-
detailRows.push(buildPanelLine(width, [
|
|
253
|
-
[' Title: ', C.label],
|
|
254
|
-
[selected.title, C.value],
|
|
255
|
-
[' Status: ', C.label],
|
|
256
|
-
[selected.status, statusColor(selected.status)],
|
|
257
|
-
[' Kind: ', C.label],
|
|
258
|
-
[kindLabel(selected.kind), C.value],
|
|
259
|
-
]));
|
|
260
|
-
detailRows.push(buildPanelLine(width, [
|
|
261
|
-
[' Owner: ', C.label],
|
|
262
|
-
[selected.owner, C.value],
|
|
263
|
-
[' Cancellable: ', C.label],
|
|
264
|
-
[selected.cancellable ? 'yes' : 'no', selected.cancellable ? C.running : C.failed],
|
|
265
|
-
[' Queue: ', C.label],
|
|
266
|
-
[formatWhen(selected.queuedAt), C.dim],
|
|
267
|
-
]));
|
|
268
|
-
detailRows.push(buildPanelLine(width, [
|
|
269
|
-
[' Started: ', C.label],
|
|
270
|
-
[formatWhen(selected.startedAt), C.dim],
|
|
271
|
-
[' Ended: ', C.label],
|
|
272
|
-
[formatWhen(selected.endedAt), C.dim],
|
|
273
|
-
[' Duration: ', C.label],
|
|
274
|
-
[formatDuration(selected.startedAt, selected.endedAt), C.dim],
|
|
275
|
-
]));
|
|
276
|
-
if (descriptor?.mode || descriptor?.family || descriptor?.source) {
|
|
277
|
-
detailRows.push(buildPanelLine(width, [
|
|
278
|
-
[' Mode: ', C.label],
|
|
279
|
-
[descriptor?.mode ?? 'n/a', C.value],
|
|
280
|
-
[' Family: ', C.label],
|
|
281
|
-
[descriptor?.family ?? 'n/a', C.info],
|
|
282
|
-
[' Origin: ', C.label],
|
|
283
|
-
[descriptorOriginLabel(descriptor?.source), C.dim],
|
|
284
|
-
]));
|
|
285
|
-
}
|
|
286
|
-
if (descriptor?.reviewMode || descriptor?.executionProtocol || descriptor?.template) {
|
|
287
|
-
detailRows.push(buildPanelLine(width, [
|
|
288
|
-
[' Review: ', C.label],
|
|
289
|
-
[descriptor?.reviewMode ?? 'n/a', C.value],
|
|
290
|
-
[' Protocol: ', C.label],
|
|
291
|
-
[descriptor?.executionProtocol ?? 'n/a', C.value],
|
|
292
|
-
[' Template: ', C.label],
|
|
293
|
-
[descriptor?.template ?? 'n/a', C.dim],
|
|
294
|
-
]));
|
|
295
|
-
}
|
|
296
|
-
if (selected.correlationId || selected.turnId) {
|
|
297
|
-
detailRows.push(buildPanelLine(width, [
|
|
298
|
-
[' Correlation: ', C.label],
|
|
299
|
-
[selected.correlationId ?? 'n/a', C.dim],
|
|
300
|
-
[' Turn: ', C.label],
|
|
301
|
-
[selected.turnId ?? 'n/a', C.dim],
|
|
302
|
-
]));
|
|
303
|
-
}
|
|
304
|
-
if (selected.parentTaskId || selected.childTaskIds.length > 0) {
|
|
305
|
-
detailRows.push(buildPanelLine(width, [
|
|
306
|
-
[' Parent: ', C.label],
|
|
307
|
-
[selected.parentTaskId ?? 'none', C.dim],
|
|
308
|
-
[' Children: ', C.label],
|
|
309
|
-
[selected.childTaskIds.length > 0 ? selected.childTaskIds.join(', ') : 'none', C.dim],
|
|
310
|
-
]));
|
|
311
|
-
}
|
|
312
|
-
if (selected.retryPolicy) {
|
|
313
|
-
detailRows.push(buildPanelLine(width, [
|
|
314
|
-
[' Retry: ', C.label],
|
|
315
|
-
[`${selected.retryPolicy.currentAttempt}/${selected.retryPolicy.maxAttempts} ${selected.retryPolicy.backoff}`, C.value],
|
|
316
|
-
]));
|
|
317
|
-
}
|
|
318
|
-
if (selected.error) {
|
|
319
|
-
detailRows.push(buildPanelLine(width, [
|
|
320
|
-
[' Error ', C.label],
|
|
321
|
-
[selected.error.slice(0, Math.max(0, width - 10)), C.failed],
|
|
322
|
-
]));
|
|
323
|
-
}
|
|
324
|
-
if (selected.result !== undefined) {
|
|
325
|
-
const resultText = safeJson(selected.result);
|
|
326
|
-
detailRows.push(buildPanelLine(width, [
|
|
327
|
-
[' Result: ', C.label],
|
|
328
|
-
[resultText.slice(0, Math.max(0, width - 11)), C.dim],
|
|
329
|
-
]));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const headerLines: Line[] = buildSummaryBlock(width, 'Task posture', postureLines, C);
|
|
334
|
-
|
|
335
|
-
return this.renderList(width, height, {
|
|
336
|
-
title: 'Task Control Room',
|
|
337
|
-
header: headerLines,
|
|
338
|
-
footer: [
|
|
339
|
-
...buildDetailBlock(width, 'Selected task', detailRows, C),
|
|
340
|
-
...footerLines,
|
|
341
|
-
],
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// ThinkingPanel — streams model reasoning tokens in real-time.
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
import type { Line } from '../types/grid.ts';
|
|
6
|
-
import { BasePanel } from './base-panel.ts';
|
|
7
|
-
import type { TurnEvent } from '@/runtime/index.ts';
|
|
8
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
9
|
-
import {
|
|
10
|
-
buildEmptyState,
|
|
11
|
-
buildPanelLine,
|
|
12
|
-
buildStyledPanelLine,
|
|
13
|
-
buildPanelWorkspace,
|
|
14
|
-
resolveScrollablePanelSection,
|
|
15
|
-
DEFAULT_PANEL_PALETTE,
|
|
16
|
-
type PanelWorkspaceSection,
|
|
17
|
-
} from './polish.ts';
|
|
18
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
19
|
-
|
|
20
|
-
const C = {
|
|
21
|
-
headerBg: '#1a1a2e',
|
|
22
|
-
headerFg: '#ffffff',
|
|
23
|
-
statusBar: '#222233',
|
|
24
|
-
statusFg: '#aaaaaa',
|
|
25
|
-
reasoningFg: '#aa88ff',
|
|
26
|
-
activeFg: '#cc99ff',
|
|
27
|
-
dimFg: '#555566',
|
|
28
|
-
turnLabel: '#7766bb',
|
|
29
|
-
activeLabel: '#00ffff',
|
|
30
|
-
collapsedFg: '#888899',
|
|
31
|
-
selected: '#00ffff',
|
|
32
|
-
selectedBg: '#1a2a3a',
|
|
33
|
-
} as const;
|
|
34
|
-
|
|
35
|
-
interface ReasoningBlock {
|
|
36
|
-
turnId: number;
|
|
37
|
-
content: string;
|
|
38
|
-
active: boolean; // true = currently streaming
|
|
39
|
-
collapsed: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function wrapLines(text: string, width: number): string[] {
|
|
43
|
-
const result: string[] = [];
|
|
44
|
-
const raw = text.split('\n');
|
|
45
|
-
for (const line of raw) {
|
|
46
|
-
if (line.length <= width) {
|
|
47
|
-
result.push(line);
|
|
48
|
-
} else {
|
|
49
|
-
let start = 0;
|
|
50
|
-
while (start < line.length) {
|
|
51
|
-
result.push(line.slice(start, start + width));
|
|
52
|
-
start += width;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type FlatRow = { kind: 'header'; blockIndex: number; text: string } | { kind: 'content'; text: string };
|
|
60
|
-
|
|
61
|
-
export class ThinkingPanel extends BasePanel {
|
|
62
|
-
private blocks: ReasoningBlock[] = [];
|
|
63
|
-
private nextTurnId = 1;
|
|
64
|
-
private unsubs: Array<() => void> = [];
|
|
65
|
-
private cursorIndex = 0;
|
|
66
|
-
private scrollOffset = 0;
|
|
67
|
-
private autoScroll = true;
|
|
68
|
-
private lastWidth = 80;
|
|
69
|
-
private static readonly MAX_BLOCKS = 100;
|
|
70
|
-
|
|
71
|
-
constructor(private readonly turnEvents: UiEventFeed<TurnEvent>) {
|
|
72
|
-
super('thinking', 'Thinking', 'T', 'ai');
|
|
73
|
-
this._attachBus();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
override onActivate(): void {
|
|
77
|
-
this.needsRender = true;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
override onDeactivate(): void {
|
|
81
|
-
super.onDeactivate();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
override onDestroy(): void {
|
|
85
|
-
this._detachBus();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
handleInput(key: string): boolean {
|
|
89
|
-
switch (key) {
|
|
90
|
-
case 'up': this._move(-1); this.autoScroll = false; return true;
|
|
91
|
-
case 'down': this._move(1); return true;
|
|
92
|
-
case 'pageup': this._move(-10); this.autoScroll = false; return true;
|
|
93
|
-
case 'pagedown': this._move(10); return true;
|
|
94
|
-
case 'return': this._toggleCollapse(); return true;
|
|
95
|
-
case 'g': this.autoScroll = true; this.markDirty(); return true;
|
|
96
|
-
default: return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
render(width: number, height: number): Line[] {
|
|
101
|
-
if (height <= 0 || width <= 0) return [];
|
|
102
|
-
this.lastWidth = width;
|
|
103
|
-
|
|
104
|
-
const hasActive = this.blocks.some(b => b.active);
|
|
105
|
-
const flat = this._buildFlat(width);
|
|
106
|
-
const title = hasActive ? ' Thinking [streaming]' : ` Thinking [${this.blocks.length} blocks]`;
|
|
107
|
-
const footerLines = [
|
|
108
|
-
buildPanelLine(width, [
|
|
109
|
-
[' Up/Down', DEFAULT_PANEL_PALETTE.info],
|
|
110
|
-
[' scroll', DEFAULT_PANEL_PALETTE.dim],
|
|
111
|
-
[' Enter', DEFAULT_PANEL_PALETTE.info],
|
|
112
|
-
[' collapse', DEFAULT_PANEL_PALETTE.dim],
|
|
113
|
-
[' g', DEFAULT_PANEL_PALETTE.info],
|
|
114
|
-
[' jump to end', DEFAULT_PANEL_PALETTE.dim],
|
|
115
|
-
[this.autoScroll ? ' auto-scroll ON' : ' manual', this.autoScroll ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.warn],
|
|
116
|
-
]),
|
|
117
|
-
];
|
|
118
|
-
|
|
119
|
-
if (flat.length === 0) {
|
|
120
|
-
return buildPanelWorkspace(width, height, {
|
|
121
|
-
title,
|
|
122
|
-
intro: 'Live reasoning blocks stream here while the model is actively thinking.',
|
|
123
|
-
sections: [
|
|
124
|
-
{
|
|
125
|
-
title: 'Reasoning',
|
|
126
|
-
lines: buildEmptyState(
|
|
127
|
-
width,
|
|
128
|
-
' No reasoning content yet',
|
|
129
|
-
'When the model emits thinking or reasoning deltas, they accumulate here in expandable blocks.',
|
|
130
|
-
[],
|
|
131
|
-
DEFAULT_PANEL_PALETTE,
|
|
132
|
-
),
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
footerLines,
|
|
136
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (this.autoScroll) {
|
|
141
|
-
this.cursorIndex = Math.max(0, flat.length - 1);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, flat.length - 1)));
|
|
145
|
-
|
|
146
|
-
const summary: PanelWorkspaceSection = {
|
|
147
|
-
title: 'Summary',
|
|
148
|
-
lines: [
|
|
149
|
-
buildPanelLine(width, [
|
|
150
|
-
[' Blocks ', DEFAULT_PANEL_PALETTE.label],
|
|
151
|
-
[String(this.blocks.length), DEFAULT_PANEL_PALETTE.value],
|
|
152
|
-
[' Active ', DEFAULT_PANEL_PALETTE.label],
|
|
153
|
-
[String(this.blocks.filter((block) => block.active).length), hasActive ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
154
|
-
[' Mode ', DEFAULT_PANEL_PALETTE.label],
|
|
155
|
-
[this.autoScroll ? 'auto-scroll' : 'manual', this.autoScroll ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.info],
|
|
156
|
-
]),
|
|
157
|
-
],
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const selectedRow = flat[this.cursorIndex];
|
|
161
|
-
const selectedSection: PanelWorkspaceSection = {
|
|
162
|
-
title: 'Selected',
|
|
163
|
-
lines: [
|
|
164
|
-
buildPanelLine(width, [[' Row Type ', DEFAULT_PANEL_PALETTE.label], [selectedRow?.kind ?? 'none', DEFAULT_PANEL_PALETTE.value]]),
|
|
165
|
-
],
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const reasoningSection = resolveScrollablePanelSection(width, height, {
|
|
169
|
-
intro: 'Live reasoning blocks stream here while the model is actively thinking.',
|
|
170
|
-
footerLines,
|
|
171
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
172
|
-
beforeSections: [summary],
|
|
173
|
-
section: {
|
|
174
|
-
title: 'Reasoning',
|
|
175
|
-
scrollableLines: flat.map((row, index) => this._renderRow(width, row, index === this.cursorIndex)),
|
|
176
|
-
selectedIndex: this.cursorIndex,
|
|
177
|
-
scrollOffset: this.scrollOffset,
|
|
178
|
-
minRows: 8,
|
|
179
|
-
},
|
|
180
|
-
afterSections: [selectedSection],
|
|
181
|
-
});
|
|
182
|
-
this.scrollOffset = reasoningSection.scrollOffset;
|
|
183
|
-
|
|
184
|
-
return buildPanelWorkspace(width, height, {
|
|
185
|
-
title,
|
|
186
|
-
intro: 'Live reasoning blocks stream here while the model is actively thinking.',
|
|
187
|
-
sections: [
|
|
188
|
-
summary,
|
|
189
|
-
reasoningSection.section,
|
|
190
|
-
selectedSection,
|
|
191
|
-
],
|
|
192
|
-
footerLines,
|
|
193
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
198
|
-
const bg = isCursor ? C.selectedBg : '';
|
|
199
|
-
if (row.kind === 'header') {
|
|
200
|
-
const indicator = this.blocks[row.blockIndex]?.collapsed ? '▸' : '▾';
|
|
201
|
-
const active = this.blocks[row.blockIndex]?.active;
|
|
202
|
-
const bullet = active ? '\u25cf ' : '\u25e6 ';
|
|
203
|
-
return buildStyledPanelLine(width, [
|
|
204
|
-
{ text: truncateDisplay(` ${bullet}${row.text} ${indicator}`, width), fg: active ? C.activeLabel : C.turnLabel, bg, bold: true },
|
|
205
|
-
]);
|
|
206
|
-
}
|
|
207
|
-
return buildStyledPanelLine(width, [
|
|
208
|
-
{ text: ' ', fg: C.reasoningFg, bg },
|
|
209
|
-
{ text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: isCursor ? C.activeFg : C.reasoningFg, bg },
|
|
210
|
-
]);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private _buildFlat(width: number): FlatRow[] {
|
|
214
|
-
const rows: FlatRow[] = [];
|
|
215
|
-
for (let i = 0; i < this.blocks.length; i++) {
|
|
216
|
-
const block = this.blocks[i]!;
|
|
217
|
-
const turnLabel = `Turn ${block.turnId}${block.active ? ' (streaming)' : ''}`;
|
|
218
|
-
rows.push({ kind: 'header', blockIndex: i, text: turnLabel });
|
|
219
|
-
if (!block.collapsed) {
|
|
220
|
-
const wrapped = wrapLines(block.content || '(empty)', Math.max(1, width - 2));
|
|
221
|
-
for (const line of wrapped) {
|
|
222
|
-
rows.push({ kind: 'content', text: line });
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return rows;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private _toggleCollapse(): void {
|
|
230
|
-
// Find the header row at or before cursorIndex
|
|
231
|
-
const flat = this._buildFlat(this.lastWidth);
|
|
232
|
-
for (let i = this.cursorIndex; i >= 0; i--) {
|
|
233
|
-
const row = flat[i];
|
|
234
|
-
if (row?.kind === 'header') {
|
|
235
|
-
const block = this.blocks[row.blockIndex];
|
|
236
|
-
if (block) {
|
|
237
|
-
block.collapsed = !block.collapsed;
|
|
238
|
-
this.markDirty();
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private _move(delta: number): void {
|
|
246
|
-
const flat = this._buildFlat(this.lastWidth);
|
|
247
|
-
if (flat.length === 0) return;
|
|
248
|
-
this.cursorIndex = Math.max(0, Math.min(flat.length - 1, this.cursorIndex + delta));
|
|
249
|
-
this.markDirty();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private _attachBus(): void {
|
|
253
|
-
if (this.unsubs.length > 0) return;
|
|
254
|
-
|
|
255
|
-
let currentBlock: ReasoningBlock | null = null;
|
|
256
|
-
|
|
257
|
-
this.unsubs.push(this.turnEvents.on('STREAM_START', () => {
|
|
258
|
-
const block: ReasoningBlock = {
|
|
259
|
-
turnId: this.nextTurnId++,
|
|
260
|
-
content: '',
|
|
261
|
-
active: true,
|
|
262
|
-
collapsed: false,
|
|
263
|
-
};
|
|
264
|
-
// Cap block count to prevent unbounded growth
|
|
265
|
-
if (this.blocks.length >= ThinkingPanel.MAX_BLOCKS) {
|
|
266
|
-
this.blocks.shift();
|
|
267
|
-
}
|
|
268
|
-
this.blocks.push(block);
|
|
269
|
-
currentBlock = block;
|
|
270
|
-
this.autoScroll = true;
|
|
271
|
-
this.markDirty();
|
|
272
|
-
}));
|
|
273
|
-
|
|
274
|
-
this.unsubs.push(this.turnEvents.on('STREAM_DELTA', (env) => {
|
|
275
|
-
const reasoning = env.reasoning;
|
|
276
|
-
if (reasoning && currentBlock) {
|
|
277
|
-
currentBlock.content += reasoning;
|
|
278
|
-
this.autoScroll = true;
|
|
279
|
-
this.markDirty();
|
|
280
|
-
}
|
|
281
|
-
}));
|
|
282
|
-
|
|
283
|
-
this.unsubs.push(this.turnEvents.on('STREAM_END', () => {
|
|
284
|
-
if (currentBlock) {
|
|
285
|
-
currentBlock.active = false;
|
|
286
|
-
currentBlock = null;
|
|
287
|
-
this.markDirty();
|
|
288
|
-
}
|
|
289
|
-
}));
|
|
290
|
-
|
|
291
|
-
this.unsubs.push(this.turnEvents.on('TURN_COMPLETED', () => {
|
|
292
|
-
if (currentBlock) {
|
|
293
|
-
currentBlock.active = false;
|
|
294
|
-
currentBlock = null;
|
|
295
|
-
this.markDirty();
|
|
296
|
-
}
|
|
297
|
-
}));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private _detachBus(): void {
|
|
301
|
-
for (const unsub of this.unsubs) unsub();
|
|
302
|
-
this.unsubs = [];
|
|
303
|
-
}
|
|
304
|
-
}
|