@pellux/goodvibes-tui 0.26.0 → 0.28.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 +40 -0
- package/README.md +12 -7
- package/docs/foundation-artifacts/operator-contract.json +4 -1
- package/package.json +2 -2
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +53 -8
- package/src/core/session-recovery.ts +26 -18
- package/src/core/system-message-router.ts +42 -26
- package/src/core/turn-event-wiring.ts +13 -16
- package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
- package/src/daemon/handlers/inbox/index.ts +2 -1
- package/src/daemon/handlers/inbox/poller.ts +2 -1
- package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
- package/src/daemon/handlers/inbox/providers/email.ts +2 -1
- package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
- package/src/daemon/handlers/triage/integration.ts +2 -1
- package/src/daemon/handlers/triage/pipeline.ts +2 -1
- package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
- package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
- package/src/export/gist-uploader.ts +3 -1
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +2 -1
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +2 -4
- package/src/input/commands/health-runtime.ts +13 -7
- package/src/input/commands/knowledge.ts +2 -1
- package/src/input/commands/runtime-services.ts +40 -10
- package/src/input/handler-command-route.ts +1 -1
- package/src/input/handler-feed-routes.ts +61 -4
- package/src/input/handler-feed.ts +13 -0
- package/src/input/handler-interactions.ts +2 -1
- package/src/input/handler-modal-routes.ts +2 -1
- package/src/input/handler-onboarding-cloudflare.ts +2 -1
- package/src/input/handler-onboarding.ts +8 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-ui-state.ts +5 -1
- package/src/input/keybindings.ts +5 -0
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +10 -0
- package/src/input/tts-settings-actions.ts +2 -1
- package/src/main.ts +52 -60
- package/src/panels/agent-inspector-panel.ts +90 -15
- package/src/panels/agent-inspector-shared.ts +3 -3
- package/src/panels/agent-logs-panel.ts +149 -72
- package/src/panels/approval-panel.ts +146 -95
- package/src/panels/automation-control-panel.ts +24 -1
- package/src/panels/base-panel.ts +28 -3
- package/src/panels/builtin/agent.ts +3 -1
- package/src/panels/builtin/development.ts +2 -1
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/cockpit-panel.ts +24 -5
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +108 -43
- package/src/panels/context-visualizer-panel.ts +49 -15
- package/src/panels/control-plane-panel.ts +27 -1
- package/src/panels/cost-tracker-panel.ts +87 -65
- package/src/panels/debug-panel.ts +61 -37
- package/src/panels/diff-panel.ts +59 -30
- package/src/panels/docs-panel.ts +30 -24
- package/src/panels/eval-panel.ts +130 -81
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +42 -42
- package/src/panels/file-preview-panel.ts +11 -3
- package/src/panels/forensics-panel.ts +27 -13
- package/src/panels/git-panel.ts +65 -84
- package/src/panels/hooks-panel.ts +36 -2
- package/src/panels/incident-review-panel.ts +31 -10
- package/src/panels/intelligence-panel.ts +152 -71
- package/src/panels/knowledge-graph-panel.ts +89 -27
- package/src/panels/local-auth-panel.ts +17 -11
- package/src/panels/marketplace-panel.ts +33 -13
- package/src/panels/memory-panel.ts +7 -5
- package/src/panels/ops-control-panel.ts +69 -7
- package/src/panels/ops-strategy-panel.ts +61 -43
- package/src/panels/orchestration-panel.ts +34 -4
- package/src/panels/panel-list-panel.ts +33 -8
- package/src/panels/panel-manager.ts +23 -4
- package/src/panels/plan-dashboard-panel.ts +183 -66
- package/src/panels/plugins-panel.ts +48 -10
- package/src/panels/policy-panel.ts +60 -23
- package/src/panels/polish-core.ts +157 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +44 -145
- package/src/panels/project-planning-panel.ts +27 -4
- package/src/panels/provider-accounts-panel.ts +55 -18
- package/src/panels/provider-health-panel.ts +118 -87
- package/src/panels/provider-stats-panel.ts +47 -22
- package/src/panels/qr-panel.ts +23 -11
- package/src/panels/remote-panel.ts +12 -5
- package/src/panels/routes-panel.ts +27 -5
- package/src/panels/sandbox-panel.ts +62 -27
- package/src/panels/schedule-panel.ts +44 -24
- package/src/panels/scrollable-list-panel.ts +124 -10
- package/src/panels/security-panel.ts +72 -20
- package/src/panels/services-panel.ts +68 -22
- package/src/panels/session-browser-panel.ts +42 -26
- package/src/panels/session-maintenance.ts +2 -2
- package/src/panels/settings-sync-panel.ts +30 -15
- package/src/panels/skills-panel.ts +2 -1
- package/src/panels/subscription-panel.ts +21 -3
- package/src/panels/symbol-outline-panel.ts +40 -47
- package/src/panels/system-messages-panel.ts +60 -27
- package/src/panels/tasks-panel.ts +36 -14
- package/src/panels/thinking-panel.ts +32 -36
- package/src/panels/token-budget-panel.ts +80 -53
- package/src/panels/tool-inspector-panel.ts +37 -39
- package/src/panels/types.ts +25 -0
- package/src/panels/watchers-panel.ts +25 -5
- package/src/panels/work-plan-panel.ts +127 -35
- package/src/panels/worktree-panel.ts +65 -20
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +53 -147
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/modal-utils.ts +0 -10
- package/src/renderer/model-picker-overlay.ts +9 -4
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-modal.ts +2 -2
- package/src/renderer/progress.ts +3 -2
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/ui-factory.ts +4 -6
- package/src/runtime/bootstrap-command-context.ts +3 -0
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +31 -31
- package/src/runtime/bootstrap-shell.ts +1 -0
- package/src/runtime/onboarding/apply.ts +4 -3
- package/src/runtime/onboarding/snapshot.ts +4 -3
- package/src/runtime/process-lifecycle.ts +195 -0
- package/src/runtime/services.ts +4 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/runtime/wrfc-persistence.ts +20 -5
- package/src/shell/ui-openers.ts +3 -2
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/verification/live-verifier.ts +4 -3
- package/src/version.ts +1 -1
- package/src/core/context-auto-compact.ts +0 -110
- package/src/panels/panel-picker.ts +0 -106
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { UiRuntimeEvents } from '@/runtime/index.ts';
|
|
2
2
|
import { buildPersistedSessionContext, persistConversation } from '@/runtime/index.ts';
|
|
3
|
-
import { maybeAutoCompact } from './context-auto-compact.ts';
|
|
4
3
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
6
5
|
import type { HookDispatcher, HookPhase, HookCategory, HookEventPath } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
@@ -20,6 +19,7 @@ interface TurnOrchestrator {
|
|
|
20
19
|
/** Minimal provider registry surface required by turn-event wiring. */
|
|
21
20
|
interface TurnProviderRegistry {
|
|
22
21
|
getCurrentModel(): { readonly contextWindow: number };
|
|
22
|
+
getContextWindowForModel(model: { readonly contextWindow: number }): number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Minimal config manager surface required by turn-event wiring. */
|
|
@@ -80,17 +80,21 @@ export interface WireTurnEventHandlersResult {
|
|
|
80
80
|
* Responsibilities:
|
|
81
81
|
* - Auto-save conversation to persistent store after each LLM turn
|
|
82
82
|
* - Fire the Lifecycle:session:save hook
|
|
83
|
-
* - Trigger auto-compact when context usage exceeds the configured threshold
|
|
84
83
|
* - Refresh git status after turns and tool results
|
|
85
84
|
*
|
|
85
|
+
* Note: auto-compaction is intentionally NOT performed here. The SDK
|
|
86
|
+
* Orchestrator already runs post-turn context maintenance every turn
|
|
87
|
+
* (handlePostTurnContextMaintenance) using the resolved context window and
|
|
88
|
+
* its own getAutoCompactDecision; re-triggering it from the TUI duplicated
|
|
89
|
+
* that work and raced the SDK's compaction on the shared message array.
|
|
90
|
+
*
|
|
86
91
|
* Returns refreshGit (callable externally) and unsubs (push into parent unsubs).
|
|
87
92
|
*/
|
|
88
93
|
export function wireTurnEventHandlers(
|
|
89
94
|
options: WireTurnEventHandlersOptions,
|
|
90
95
|
): WireTurnEventHandlersResult {
|
|
91
96
|
const {
|
|
92
|
-
events, conversation, runtime,
|
|
93
|
-
providerRegistry, systemMessageRouter, hookDispatcher,
|
|
97
|
+
events, conversation, runtime, configManager, hookDispatcher,
|
|
94
98
|
workingDir, homeDirectory, sessionManager, gitStatusProvider,
|
|
95
99
|
lastGitInfoRef, buildSessionContinuityHints, render, webhookNotifier,
|
|
96
100
|
_clock = Date.now,
|
|
@@ -161,18 +165,11 @@ export function wireTurnEventHandlers(
|
|
|
161
165
|
} catch { /* best-effort */ }
|
|
162
166
|
logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) });
|
|
163
167
|
}
|
|
164
|
-
// Auto-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
providerRegistry: providerRegistry as Parameters<typeof maybeAutoCompact>[0]['providerRegistry'],
|
|
170
|
-
systemMessageRouter: systemMessageRouter as Parameters<typeof maybeAutoCompact>[0]['systemMessageRouter'],
|
|
171
|
-
model: runtime.model,
|
|
172
|
-
provider: runtime.provider,
|
|
173
|
-
lastInputTokens: orchestrator.lastInputTokens,
|
|
174
|
-
contextWindow: currentModelForCompact.contextWindow,
|
|
175
|
-
}).catch((err: unknown) => logger.debug('maybeAutoCompact error', { error: summarizeError(err) }));
|
|
168
|
+
// Auto-compaction is owned by the SDK Orchestrator's post-turn maintenance
|
|
169
|
+
// (handlePostTurnContextMaintenance), which runs on every turn against the
|
|
170
|
+
// resolved context window (getContextWindowForModel) via the SDK's
|
|
171
|
+
// getAutoCompactDecision (percentage threshold + 15k safety buffer +
|
|
172
|
+
// small-window handling). The TUI must not independently re-trigger it.
|
|
176
173
|
refreshGit();
|
|
177
174
|
}));
|
|
178
175
|
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type GenerateICalInput,
|
|
27
27
|
type ParsedICalEvent,
|
|
28
28
|
} from './ics.ts';
|
|
29
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
29
30
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
31
32
|
// Configuration
|
|
@@ -586,7 +587,7 @@ export function createCalDavClient(options: CreateCalDavClientOptions): CalDavCl
|
|
|
586
587
|
eventIds.push(toRelativeHref(joinUrl(collectionPathOrRoot(collectionUrl, config.baseUrl), resourcePath)));
|
|
587
588
|
imported += 1;
|
|
588
589
|
} catch (error) {
|
|
589
|
-
const message = error instanceof HandlerError ? error.message :
|
|
590
|
+
const message = error instanceof HandlerError ? error.message : summarizeError(error);
|
|
590
591
|
errors.push(`${uid}: ${message}`);
|
|
591
592
|
}
|
|
592
593
|
}
|
|
@@ -40,6 +40,7 @@ import { InboundPoller } from './poller.ts';
|
|
|
40
40
|
import { createSlackAdapter, SLACK_PROVIDER_ID } from './providers/slack.ts';
|
|
41
41
|
import { createDiscordAdapter, DISCORD_PROVIDER_ID } from './providers/discord.ts';
|
|
42
42
|
import { createEmailAdapter, EMAIL_PROVIDER_ID } from './providers/email.ts';
|
|
43
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
43
44
|
|
|
44
45
|
export const INBOX_LIST_METHOD_ID = 'channels.inbox.list';
|
|
45
46
|
const DEFAULT_LIMIT = 50;
|
|
@@ -173,7 +174,7 @@ export function registerInboxMethods(
|
|
|
173
174
|
poller.start();
|
|
174
175
|
})().catch((error: unknown) => {
|
|
175
176
|
ctx.logger.error('inbox surface bootstrap failed', {
|
|
176
|
-
error:
|
|
177
|
+
error: summarizeError(error),
|
|
177
178
|
});
|
|
178
179
|
});
|
|
179
180
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { InboundProviderAdapter, ProviderState } from './provider-adapter.ts';
|
|
18
18
|
import type { InboxCursorStore } from './cursor-store.ts';
|
|
19
19
|
import type { HandlerLogger } from '../context.ts';
|
|
20
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
20
21
|
|
|
21
22
|
export interface ProviderStatus {
|
|
22
23
|
id: string;
|
|
@@ -115,7 +116,7 @@ export class InboundPoller {
|
|
|
115
116
|
lastPolledAt: Date.now(),
|
|
116
117
|
});
|
|
117
118
|
} catch (error) {
|
|
118
|
-
const message =
|
|
119
|
+
const message = summarizeError(error);
|
|
119
120
|
this.logger.warn('inbound poll failed', { provider: id, error: message });
|
|
120
121
|
this.setStatus(id, {
|
|
121
122
|
id,
|
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
import { POLL_CADENCE_MS } from '../provider-adapter.ts';
|
|
32
32
|
import { digestSender, toBodyPreview, toSubjectPreview } from '../mapping.ts';
|
|
33
33
|
import { resolveRouteId } from './route-util.ts';
|
|
34
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
34
35
|
|
|
35
36
|
const DISCORD_API = 'https://discord.com/api/v10';
|
|
36
37
|
// Discord epoch (2015-01-01T00:00:00Z) in ms, used to decode snowflakes.
|
|
@@ -247,5 +248,5 @@ function unavailable(error: string): ProviderPollResult {
|
|
|
247
248
|
}
|
|
248
249
|
|
|
249
250
|
function errMsg(error: unknown): string {
|
|
250
|
-
return
|
|
251
|
+
return summarizeError(error);
|
|
251
252
|
}
|
|
@@ -25,6 +25,7 @@ import { digestSender, toBodyPreview, toSubjectPreview } from '../mapping.ts';
|
|
|
25
25
|
import { resolveRouteId } from './route-util.ts';
|
|
26
26
|
import { ImapClient } from './imap-client.ts';
|
|
27
27
|
import type { ImapConfig, ImapEnvelope } from './imap-client.ts';
|
|
28
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
28
29
|
|
|
29
30
|
export const EMAIL_PROVIDER_ID = 'email';
|
|
30
31
|
export const EMAIL_HOST_KEY = 'surfaces.email.imapHost';
|
|
@@ -147,5 +148,5 @@ function unavailable(error: string): ProviderPollResult {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
function errMsg(error: unknown): string {
|
|
150
|
-
return
|
|
151
|
+
return summarizeError(error);
|
|
151
152
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// surface) so a route lookup can never crash a provider poll.
|
|
4
4
|
|
|
5
5
|
import type { AdapterContext, InboundChannelItem } from '../provider-adapter.ts';
|
|
6
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
6
7
|
|
|
7
8
|
export async function resolveRouteId(
|
|
8
9
|
ctx: AdapterContext,
|
|
@@ -16,7 +17,7 @@ export async function resolveRouteId(
|
|
|
16
17
|
} catch (error) {
|
|
17
18
|
ctx.logger.warn('route resolution failed', {
|
|
18
19
|
provider,
|
|
19
|
-
error:
|
|
20
|
+
error: summarizeError(error),
|
|
20
21
|
});
|
|
21
22
|
return undefined;
|
|
22
23
|
}
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
import { POLL_CADENCE_MS } from '../provider-adapter.ts';
|
|
31
31
|
import { digestSender, toBodyPreview, toSubjectPreview } from '../mapping.ts';
|
|
32
32
|
import { resolveRouteId } from './route-util.ts';
|
|
33
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
33
34
|
|
|
34
35
|
const SLACK_API = 'https://slack.com/api';
|
|
35
36
|
export const SLACK_PROVIDER_ID = 'slack';
|
|
@@ -258,5 +259,5 @@ function unavailable(error: string): ProviderPollResult {
|
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
function errMsg(error: unknown): string {
|
|
261
|
-
return
|
|
262
|
+
return summarizeError(error);
|
|
262
263
|
}
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
type TriageTagger,
|
|
48
48
|
type TriageTaggerOptions,
|
|
49
49
|
} from './tagger/index.ts';
|
|
50
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* Canonical id of the inbox list method whose handler we decorate. This is the
|
|
@@ -151,7 +152,7 @@ function withInboxEnrichment(ctx: HandlerContext): EnrichmentProxy {
|
|
|
151
152
|
// Triage is best-effort: a missing/locked store must never break the
|
|
152
153
|
// read-only inbox feed. Log and return the un-enriched result.
|
|
153
154
|
ctx.logger.warn('triage: inbox enrichment skipped', {
|
|
154
|
-
message:
|
|
155
|
+
message: summarizeError(error),
|
|
155
156
|
});
|
|
156
157
|
return result;
|
|
157
158
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type TriageScore,
|
|
24
24
|
type TriageScorerOptions,
|
|
25
25
|
} from './scorer.ts';
|
|
26
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
26
27
|
|
|
27
28
|
export const TRIAGE_STORE_FILE = 'inbox-triage.sqlite';
|
|
28
29
|
|
|
@@ -158,7 +159,7 @@ export async function runInboxTriage(
|
|
|
158
159
|
if (ownsStore) await store.save();
|
|
159
160
|
} catch (error) {
|
|
160
161
|
ctx.logger.error('triage: failed to persist scores', {
|
|
161
|
-
message:
|
|
162
|
+
message: summarizeError(error),
|
|
162
163
|
});
|
|
163
164
|
throw error;
|
|
164
165
|
} finally {
|
|
@@ -19,6 +19,7 @@ import { HandlerError } from '../../errors.ts';
|
|
|
19
19
|
import type { InboundChannelItem } from '../types.ts';
|
|
20
20
|
import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
|
|
21
21
|
import { discordEmojiForTag, stringMeta } from './shared.ts';
|
|
22
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
22
23
|
|
|
23
24
|
export async function applyDiscord(
|
|
24
25
|
item: InboundChannelItem,
|
|
@@ -179,7 +180,7 @@ async function fetchDiscordAppliedTags(
|
|
|
179
180
|
return current.filter((t): t is string => typeof t === 'string');
|
|
180
181
|
} catch (error) {
|
|
181
182
|
ctx.logger.warn('triage: discord thread tag read errored', {
|
|
182
|
-
message:
|
|
183
|
+
message: summarizeError(error),
|
|
183
184
|
});
|
|
184
185
|
return null;
|
|
185
186
|
}
|
|
@@ -14,6 +14,7 @@ import type { DaemonCredentialStore } from '../../credentials.ts';
|
|
|
14
14
|
import type { InboundChannelItem } from '../types.ts';
|
|
15
15
|
import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
|
|
16
16
|
import { imapKeywordForTag } from './shared.ts';
|
|
17
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
17
18
|
|
|
18
19
|
export interface ImapStoreArgs {
|
|
19
20
|
host: string;
|
|
@@ -227,7 +228,7 @@ export function imapStoreFlagOverTls(
|
|
|
227
228
|
reject(
|
|
228
229
|
err instanceof ImapStoreError
|
|
229
230
|
? err
|
|
230
|
-
: new ImapStoreError(
|
|
231
|
+
: new ImapStoreError(summarizeError(err), false),
|
|
231
232
|
);
|
|
232
233
|
return;
|
|
233
234
|
}
|
|
@@ -288,7 +289,7 @@ export function imapStoreFlagOverTls(
|
|
|
288
289
|
finish(
|
|
289
290
|
err instanceof ImapStoreError
|
|
290
291
|
? err
|
|
291
|
-
: new ImapStoreError(
|
|
292
|
+
: new ImapStoreError(summarizeError(err), false),
|
|
292
293
|
),
|
|
293
294
|
);
|
|
294
295
|
return;
|
|
@@ -318,7 +319,7 @@ export function imapStoreFlagOverTls(
|
|
|
318
319
|
};
|
|
319
320
|
|
|
320
321
|
socket.on('error', (err) =>
|
|
321
|
-
finish(new ImapStoreError(
|
|
322
|
+
finish(new ImapStoreError(summarizeError(err), true)),
|
|
322
323
|
);
|
|
323
324
|
socket.on('close', () => {
|
|
324
325
|
if (completed) finish();
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
// the URL can view it).
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
|
|
19
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
20
|
+
|
|
19
21
|
export type UploadResult =
|
|
20
22
|
| { ok: true; url: string }
|
|
21
23
|
| { ok: false; error: string };
|
|
@@ -96,7 +98,7 @@ export class GistUploadTarget implements UploadTarget {
|
|
|
96
98
|
body,
|
|
97
99
|
});
|
|
98
100
|
} catch (fetchErr: unknown) {
|
|
99
|
-
const msg =
|
|
101
|
+
const msg = summarizeError(fetchErr);
|
|
100
102
|
return { ok: false, error: `Network error: ${msg}` };
|
|
101
103
|
}
|
|
102
104
|
|
|
@@ -139,6 +139,7 @@ export interface CommandSessionServices {
|
|
|
139
139
|
readonly sessionManager?: import('@pellux/goodvibes-sdk/platform/sessions').SessionManager;
|
|
140
140
|
readonly sessionMemoryStore?: import('@pellux/goodvibes-sdk/platform/core').SessionMemoryStore;
|
|
141
141
|
readonly sessionLineageTracker?: import('@pellux/goodvibes-sdk/platform/core').SessionLineageTracker;
|
|
142
|
+
readonly wrfcController?: import('@pellux/goodvibes-sdk/platform/agents').WrfcController;
|
|
142
143
|
readonly changeTracker?: import('@pellux/goodvibes-sdk/platform/sessions').SessionChangeTracker;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../runtime/cloudflare-control-plane.ts';
|
|
12
12
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
13
13
|
import { requireShellPaths } from './runtime-services.ts';
|
|
14
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
15
|
|
|
15
16
|
interface ParsedCloudflareArgs {
|
|
16
17
|
readonly positional: readonly string[];
|
|
@@ -366,5 +367,5 @@ function formatCloudflareError(error: unknown): string {
|
|
|
366
367
|
if (error instanceof CloudflareDaemonRouteError) {
|
|
367
368
|
return `${error.message} (HTTP ${error.status}, ${error.code})`;
|
|
368
369
|
}
|
|
369
|
-
return
|
|
370
|
+
return summarizeError(error);
|
|
370
371
|
}
|
|
@@ -109,8 +109,9 @@ async function handleCompare(args: string[], context: CommandContext): Promise<v
|
|
|
109
109
|
// ── /eval gate ────────────────────────────────────────────────────────────────
|
|
110
110
|
|
|
111
111
|
async function handleGate(args: string[], context: CommandContext): Promise<void> {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
112
|
+
const positionals = args.filter(a => !a.startsWith('--'));
|
|
113
|
+
const suiteName = positionals[0];
|
|
114
|
+
const baselineFile = positionals[1] ?? '.goodvibes/eval/baseline.json';
|
|
114
115
|
const saveFlag = args.includes('--save-baseline');
|
|
115
116
|
const projectRoot = requireShellPaths(context).workingDirectory;
|
|
116
117
|
|
|
@@ -141,7 +142,7 @@ async function handleGate(args: string[], context: CommandContext): Promise<void
|
|
|
141
142
|
context.print(formatGateResult(gate));
|
|
142
143
|
|
|
143
144
|
if (saveFlag || !baseline) {
|
|
144
|
-
const label =
|
|
145
|
+
const label = positionals[0] ?? 'latest';
|
|
145
146
|
const newBaseline = captureBaseline(label, [fresh]);
|
|
146
147
|
try {
|
|
147
148
|
await writeBaseline(baselineFile, newBaseline, projectRoot);
|
|
@@ -2,7 +2,7 @@ import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core'
|
|
|
2
2
|
import { evaluateSessionMaintenance, formatSessionMaintenanceLines, getGuidanceMode } from '@/runtime/index.ts';
|
|
3
3
|
import { dismissGuidance, evaluateContextualGuidance, formatGuidanceItems, resetGuidance } from '@/runtime/index.ts';
|
|
4
4
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { requireReadModels, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
|
|
6
6
|
|
|
7
7
|
export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void {
|
|
8
8
|
registry.register({
|
|
@@ -68,8 +68,6 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const providerApi = requireProviderApi(ctx);
|
|
72
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
73
71
|
const llmMessages = ctx.session.conversationManager.getMessagesForLLM();
|
|
74
72
|
const readModels = requireReadModels(ctx);
|
|
75
73
|
const session = readModels.session.getSnapshot();
|
|
@@ -80,7 +78,7 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
|
|
|
80
78
|
const maintenance = evaluateSessionMaintenance({
|
|
81
79
|
configManager: ctx.platform.configManager,
|
|
82
80
|
currentTokens: estimateConversationTokens(llmMessages),
|
|
83
|
-
contextWindow:
|
|
81
|
+
contextWindow: ctx.provider.providerRegistry.getContextWindowForModel(ctx.provider.providerRegistry.getCurrentModel()),
|
|
84
82
|
messageCount: llmMessages.length,
|
|
85
83
|
sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
|
|
86
84
|
session: session.session,
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
openCommandPanel,
|
|
14
14
|
requireLocalUserAuthManager,
|
|
15
15
|
requireOperatorClient,
|
|
16
|
-
requireProviderApi,
|
|
17
16
|
requireReadModels,
|
|
18
17
|
requireSecretsManager,
|
|
19
18
|
requireServiceRegistry,
|
|
@@ -240,15 +239,19 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
240
239
|
|
|
241
240
|
if (sub === 'maintenance') {
|
|
242
241
|
const session = readModels.session.getSnapshot();
|
|
243
|
-
const
|
|
244
|
-
|
|
242
|
+
const providerRegistry = ctx.provider.providerRegistry;
|
|
243
|
+
// Resolve the context window the same way the Tokens panel does so the
|
|
244
|
+
// maintenance usage %/remaining agree across every diagnostics surface.
|
|
245
|
+
const contextWindow = providerRegistry.getContextWindowForModel(
|
|
246
|
+
providerRegistry.getCurrentModel(),
|
|
247
|
+
);
|
|
245
248
|
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
246
249
|
? ctx.session.conversationManager.getMessagesForLLM()
|
|
247
250
|
: [];
|
|
248
251
|
const maintenance = evaluateSessionMaintenance({
|
|
249
252
|
configManager: ctx.platform.configManager,
|
|
250
253
|
currentTokens: estimateConversationTokens(llmMessages),
|
|
251
|
-
contextWindow
|
|
254
|
+
contextWindow,
|
|
252
255
|
messageCount: llmMessages.length,
|
|
253
256
|
sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
|
|
254
257
|
session: session.session,
|
|
@@ -386,12 +389,15 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
386
389
|
}
|
|
387
390
|
|
|
388
391
|
const session = readModels.session.getSnapshot();
|
|
389
|
-
const
|
|
390
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
392
|
+
const providerRegistry = ctx.provider.providerRegistry;
|
|
391
393
|
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
392
394
|
? ctx.session.conversationManager.getMessagesForLLM()
|
|
393
395
|
: [];
|
|
394
|
-
|
|
396
|
+
// Resolve the context window the same way the Tokens panel does so the
|
|
397
|
+
// maintenance usage %/remaining agree across every diagnostics surface.
|
|
398
|
+
const contextWindow = providerRegistry.getContextWindowForModel(
|
|
399
|
+
providerRegistry.getCurrentModel(),
|
|
400
|
+
);
|
|
395
401
|
const maintenance = evaluateSessionMaintenance({
|
|
396
402
|
configManager: ctx.platform.configManager,
|
|
397
403
|
currentTokens: estimateConversationTokens(llmMessages),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { KnowledgeService } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
2
2
|
import type { CommandContext, SlashCommand } from '../command-registry.ts';
|
|
3
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
3
4
|
|
|
4
5
|
const KNOWLEDGE_REVIEW_ACTIONS = ['accept', 'reject', 'resolve', 'reopen', 'edit', 'forget'] as const;
|
|
5
6
|
|
|
@@ -397,7 +398,7 @@ export const knowledgeCommand: SlashCommand = {
|
|
|
397
398
|
...(result.appliedFacts ? [` applied facts: ${Object.keys(result.appliedFacts).join(', ') || 'none'}`] : []),
|
|
398
399
|
].join('\n'));
|
|
399
400
|
} catch (error) {
|
|
400
|
-
context.print(`[knowledge] ${
|
|
401
|
+
context.print(`[knowledge] ${summarizeError(error)}`);
|
|
401
402
|
}
|
|
402
403
|
break;
|
|
403
404
|
}
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
CommandSessionServices,
|
|
8
8
|
CommandWorkspaceServices,
|
|
9
9
|
} from '../command-registry.ts';
|
|
10
|
-
import { getLastCompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
|
|
10
|
+
import { compactSmallWindow, getLastCompactionEvent, SMALL_WINDOW_THRESHOLD } from '@pellux/goodvibes-sdk/platform/core';
|
|
11
11
|
import type { CompactionContext, CompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
|
|
12
12
|
import type { UiReadModels } from '../../runtime/ui-read-models.ts';
|
|
13
13
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
@@ -244,23 +244,53 @@ export function requireProviderApi(context: CommandContext): ProviderApi {
|
|
|
244
244
|
* change).
|
|
245
245
|
*/
|
|
246
246
|
export async function compactConversation(context: CommandContext): Promise<CompactionEvent | null> {
|
|
247
|
+
const conversationManager = context.session.conversationManager;
|
|
248
|
+
const providerRegistry = context.provider.providerRegistry;
|
|
249
|
+
// Resolve the live context window for the current model so manual /compact
|
|
250
|
+
// matches the auto-compaction path (which scales behaviour by window size)
|
|
251
|
+
// instead of passing a meaningless 0.
|
|
252
|
+
const contextWindow = providerRegistry.getContextWindowForModel(
|
|
253
|
+
providerRegistry.getCurrentModel(),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Small-window models lack room for the structured (multi-LLM) extraction
|
|
257
|
+
// pipeline, so mirror the SDK auto-compaction path and degrade to the
|
|
258
|
+
// simplified "keep the most recent messages" compaction.
|
|
259
|
+
if (contextWindow > 0 && contextWindow < SMALL_WINDOW_THRESHOLD) {
|
|
260
|
+
const compacted = compactSmallWindow(conversationManager.getMessagesForLLM(), 10);
|
|
261
|
+
conversationManager.replaceMessagesForLLM(compacted);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
247
265
|
const eventBefore = getLastCompactionEvent();
|
|
248
266
|
const sessionMemories = context.session.sessionMemoryStore?.list() ?? [];
|
|
267
|
+
const lineageTracker = context.session.sessionLineageTracker;
|
|
268
|
+
const agentManager = context.ops.agentManager;
|
|
269
|
+
const planManager = context.ops.planManager;
|
|
249
270
|
const compactionCtx: CompactionContext = {
|
|
250
|
-
messages:
|
|
271
|
+
messages: conversationManager.getMessagesForLLM(),
|
|
251
272
|
sessionMemories,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
// Mirror buildAutoCompactionContext: pass the running/pending agents, the
|
|
274
|
+
// active execution plan, and the session-lineage log so the handoff summary
|
|
275
|
+
// and lineage section are populated correctly (not "compaction #0").
|
|
276
|
+
agents:
|
|
277
|
+
agentManager
|
|
278
|
+
?.exportState()
|
|
279
|
+
.filter((agent) => agent.status === 'running' || agent.status === 'pending') ?? [],
|
|
280
|
+
// Mirror buildAutoCompactionContext: include the live WRFC chains (wired
|
|
281
|
+
// through context.session.wrfcController) so the handoff summary's
|
|
282
|
+
// orchestration section matches the SDK auto-compaction path.
|
|
283
|
+
wrfcChains: context.session.wrfcController?.listChains() ?? [],
|
|
284
|
+
activePlan: planManager?.getActive(context.session.runtime.sessionId) ?? null,
|
|
285
|
+
lineageEntries: lineageTracker?.getEntries() ?? [],
|
|
286
|
+
compactionCount: lineageTracker?.getCompactionCount() ?? 0,
|
|
287
|
+
contextWindow,
|
|
258
288
|
trigger: 'manual',
|
|
259
289
|
extractionModelId: context.session.runtime.model,
|
|
260
290
|
extractionProvider: context.session.runtime.provider,
|
|
261
291
|
};
|
|
262
|
-
await
|
|
263
|
-
|
|
292
|
+
await conversationManager.compact(
|
|
293
|
+
providerRegistry,
|
|
264
294
|
context.session.runtime.model,
|
|
265
295
|
'manual',
|
|
266
296
|
context.session.runtime.provider,
|
|
@@ -117,7 +117,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
|
|
|
117
117
|
return true;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
return token.logicalName !== 'left' && token.logicalName !== 'right';
|
|
120
|
+
return token.logicalName !== 'left' && token.logicalName !== 'right' && token.logicalName !== 'home' && token.logicalName !== 'end';
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function withPanelFocusSync(context: CommandContext, state: CommandModeRouteState): CommandContext {
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
} from './handler-prompt-buffer.ts';
|
|
14
14
|
import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, registerPaste } from './handler-content-actions.ts';
|
|
15
15
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
16
|
+
import { renderPanelWorkspaceBar } from '../renderer/panel-workspace-bar.ts';
|
|
17
|
+
import type { TabHitRegion } from '../renderer/tab-strip.ts';
|
|
16
18
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
17
19
|
import type { KillRing } from './kill-ring.ts';
|
|
18
20
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -84,6 +86,23 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
84
86
|
state.cyclePanelTab('prev');
|
|
85
87
|
return { handled: true, panelFocused };
|
|
86
88
|
}
|
|
89
|
+
if (kb.matches('panel-focus-toggle', token)) {
|
|
90
|
+
// Switch keyboard focus between the top and bottom panes (no-op when
|
|
91
|
+
// there is no visible, non-empty bottom pane).
|
|
92
|
+
state.panelManager.togglePaneFocus();
|
|
93
|
+
state.requestRender();
|
|
94
|
+
return { handled: true, panelFocused };
|
|
95
|
+
}
|
|
96
|
+
// Alt+1..9 — jump directly to the Nth workspace tab (across both panes).
|
|
97
|
+
// The tokenizer maps the Alt modifier onto `meta`, so an Alt-held digit
|
|
98
|
+
// arrives as { logicalName: '1'..'9', meta: true }. Gating on the modifier
|
|
99
|
+
// keeps plain digits reaching the focused panel.
|
|
100
|
+
if (token.meta && !token.ctrl && /^[1-9]$/.test(token.logicalName ?? '')) {
|
|
101
|
+
const index = Number(token.logicalName) - 1;
|
|
102
|
+
state.panelManager.activateWorkspaceIndex(index);
|
|
103
|
+
state.requestRender();
|
|
104
|
+
return { handled: true, panelFocused };
|
|
105
|
+
}
|
|
87
106
|
if (kb.matches('panel-close-all', token)) {
|
|
88
107
|
const pm = state.panelManager;
|
|
89
108
|
for (const p of pm.getAllOpen()) pm.close(p.id);
|
|
@@ -577,18 +596,48 @@ function getPanelUnderMouse(
|
|
|
577
596
|
return getActivePanelInPane(panelManager, 'top');
|
|
578
597
|
}
|
|
579
598
|
|
|
599
|
+
// Single consolidated workspace bar (row 0) + h-separator; the rest splits
|
|
600
|
+
// between the two panes' content.
|
|
580
601
|
const panelAreaRows = Math.max(0, layout.height - 1);
|
|
581
|
-
const contentRows = Math.max(0, panelAreaRows -
|
|
602
|
+
const contentRows = Math.max(0, panelAreaRows - 1);
|
|
582
603
|
const topContentRows = contentRows <= 1
|
|
583
604
|
? contentRows
|
|
584
605
|
: Math.max(1, Math.floor(contentRows * clampRatio(layout.verticalSplitRatio)));
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
return panelRow <= topLastRow
|
|
606
|
+
// panelRow 0 = workspace bar; rows 1..topContentRows = top pane; rest = bottom.
|
|
607
|
+
return panelRow <= topContentRows
|
|
588
608
|
? getActivePanelInPane(panelManager, 'top')
|
|
589
609
|
: getActivePanelInPane(panelManager, 'bottom');
|
|
590
610
|
}
|
|
591
611
|
|
|
612
|
+
/**
|
|
613
|
+
* If the mouse is over the consolidated workspace tab bar (the first panel
|
|
614
|
+
* row), return the index of the tab under the cursor, else null. Recomputes the
|
|
615
|
+
* tab hit regions by rendering the bar with a layout callback — cheap and keeps
|
|
616
|
+
* the click geometry in lockstep with what was drawn.
|
|
617
|
+
*/
|
|
618
|
+
function workspaceTabAtMouse(
|
|
619
|
+
panelManager: PanelManager,
|
|
620
|
+
layout: PanelMouseLayout | null,
|
|
621
|
+
row: number,
|
|
622
|
+
col: number,
|
|
623
|
+
): number | null {
|
|
624
|
+
if (
|
|
625
|
+
layout === null
|
|
626
|
+
|| !panelManager.isVisible()
|
|
627
|
+
|| panelManager.getAllOpen().length === 0
|
|
628
|
+
|| row !== layout.y // workspace bar is the first panel row
|
|
629
|
+
|| col < layout.x
|
|
630
|
+
|| col >= layout.x + layout.width
|
|
631
|
+
) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
let regions: readonly TabHitRegion[] = [];
|
|
635
|
+
renderPanelWorkspaceBar(panelManager.getWorkspaceTabs(), layout.width, true, (r) => { regions = r; });
|
|
636
|
+
const relCol = col - layout.x;
|
|
637
|
+
const hit = regions.find((rg) => relCol >= rg.startCol && relCol < rg.endCol);
|
|
638
|
+
return hit ? hit.index : null;
|
|
639
|
+
}
|
|
640
|
+
|
|
592
641
|
function scrollPanelUnderMouse(
|
|
593
642
|
state: MouseRouteState,
|
|
594
643
|
token: Extract<InputToken, { type: 'mouse' }>,
|
|
@@ -634,6 +683,14 @@ export function handleMouseToken(state: MouseRouteState, token: InputToken): {
|
|
|
634
683
|
return { handled: true, mouseDownRow, mouseDownCol };
|
|
635
684
|
}
|
|
636
685
|
if (token.button === 0 && token.action === 'press') {
|
|
686
|
+
// Click on a workspace tab activates it (checked before starting a text
|
|
687
|
+
// selection so a tab click doesn't begin a drag-select).
|
|
688
|
+
const tabIndex = workspaceTabAtMouse(state.panelManager, state.panelMouseLayout, token.row, token.col);
|
|
689
|
+
if (tabIndex !== null) {
|
|
690
|
+
state.panelManager.activateWorkspaceIndex(tabIndex);
|
|
691
|
+
state.requestRender();
|
|
692
|
+
return { handled: true, mouseDownRow, mouseDownCol };
|
|
693
|
+
}
|
|
637
694
|
mouseDownRow = token.row;
|
|
638
695
|
mouseDownCol = token.col;
|
|
639
696
|
state.selection.startSelection(token.col, viewportRow, state.scrollTop, state.viewportHeight, state.lineCount);
|
|
@@ -296,6 +296,19 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
296
296
|
context.cursorPos = shortcutState.cursorPos;
|
|
297
297
|
context.commandMode = shortcutState.commandMode;
|
|
298
298
|
context.panelFocused = shortcutState.panelFocused;
|
|
299
|
+
if (context.commandMode) {
|
|
300
|
+
if (!context.prompt.startsWith('/')) {
|
|
301
|
+
context.commandMode = false;
|
|
302
|
+
context.autocomplete?.reset();
|
|
303
|
+
} else {
|
|
304
|
+
const q = context.prompt.slice(1);
|
|
305
|
+
if (q.indexOf(' ') === -1) {
|
|
306
|
+
context.autocomplete?.update(q);
|
|
307
|
+
} else {
|
|
308
|
+
context.autocomplete?.reset();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
299
312
|
continue;
|
|
300
313
|
}
|
|
301
314
|
}
|