@pellux/goodvibes-tui 2.0.19 → 2.0.20
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 +18 -3
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli/management-commands.ts +13 -12
- package/src/core/budget-breach-notifier.ts +1 -1
- package/src/core/conversation.ts +1 -1
- package/src/input/commands/graph-runtime.ts +1 -1
- package/src/input/commands/profile-render.ts +1 -1
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/shell-core.ts +2 -2
- package/src/input/commands/workstream-runtime.ts +4 -4
- package/src/input/handler-shortcuts.ts +1 -1
- package/src/input/handler.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
- package/src/panels/agent-inspector-shared.ts +2 -2
- package/src/panels/builtin/shared.ts +1 -1
- package/src/panels/fleet-acts.ts +3 -3
- package/src/panels/fleet-spawn.ts +2 -2
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +3 -3
- package/src/panels/token-budget-panel.ts +1 -1
- package/src/renderer/compositor.ts +1 -1
- package/src/renderer/deferred-render.ts +2 -2
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/onboarding/state.ts +1 -1
- package/src/runtime/process-lifecycle.ts +1 -1
- package/src/runtime/runtime-services-types.ts +1 -1
- package/src/runtime/services.ts +1 -1
- package/src/tools/wrfc-agent-guard.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [2.0.20] - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
|
|
11
|
+
- **`goodvibes pair` mints a pairing link again.** The command invoked the
|
|
12
|
+
CLI's own gateway catalog, which is deliberately empty in this surface,
|
|
13
|
+
so every attempt failed with "Gateway method has no internal handler".
|
|
14
|
+
Pairing and its tailscale lookup now route through the daemon, the same
|
|
15
|
+
path every other hosted command uses.
|
|
16
|
+
- **`pair --name` actually names the token.** The option lookup dropped
|
|
17
|
+
the flag's dashes and never matched, so every token got the default
|
|
18
|
+
name. The lookup now takes the flag token verbatim, and a test pins it.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
7
22
|
## [2.0.19] - 2026-08-22
|
|
8
23
|
|
|
9
24
|
### Changes
|
|
@@ -16,7 +31,7 @@ All notable changes to GoodVibes TUI.
|
|
|
16
31
|
- **A dead subscription login is stamped and shown as ended**, everywhere a
|
|
17
32
|
status renders; a near-expiry token refreshes silently before the send.
|
|
18
33
|
- **A machine with no microphone is reported as that**, once, in plain
|
|
19
|
-
words, with a gentle retry every minute
|
|
34
|
+
words, with a gentle retry every minute, not a wake-detector crash loop
|
|
20
35
|
spamming the transcript and latching off. A busy device keeps prompt
|
|
21
36
|
retries and the crash latch.
|
|
22
37
|
- Pins move to sdk/terminal-shell/toolchain 2.0.22 and daemon 1.28.24;
|
|
@@ -28,7 +43,7 @@ All notable changes to GoodVibes TUI.
|
|
|
28
43
|
|
|
29
44
|
- **Session start no longer crashes behind the scenes.** Every session
|
|
30
45
|
opened with "[Error] Provider error: Cannot access 'render' before
|
|
31
|
-
initialization"
|
|
46
|
+
initialization", a startup crash inside the TUI's own wiring, mislabeled
|
|
32
47
|
as a provider problem, that killed whatever bootstrap step was mid-flight.
|
|
33
48
|
Render requests that arrive before the renderer exists are now deferred
|
|
34
49
|
into the first paint instead of throwing.
|
|
@@ -36,7 +51,7 @@ All notable changes to GoodVibes TUI.
|
|
|
36
51
|
session ended server-side used to surface as "the provider rejected your
|
|
37
52
|
API key. Run /login to re-authenticate or check your API key", which reads
|
|
38
53
|
as "your subscription isn't there" to a logged-in subscriber. It now says
|
|
39
|
-
the subscription session has ended and to sign in again
|
|
54
|
+
the subscription session has ended and to sign in again. With
|
|
40
55
|
platform runtime 2.0.21 underneath, a token OpenAI rotated early is
|
|
41
56
|
refreshed and retried before anything is surfaced at all.
|
|
42
57
|
- **Escaped errors are captioned honestly.** An unhandled rejection with no
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
GoodVibes is a terminal console for coding and operations work with an AI model. You run `goodvibes` in a project directory and get a full-screen terminal app where you talk to a model that can read and edit your files, run shell commands, search the web, and hand work off to background agents, asking your permission before anything that writes or executes.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
4
4
|
"description": "Terminal-native GoodVibes client for coding, operations, knowledge and channel workflows; a pure client of the GoodVibes daemon.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -397,14 +397,18 @@ export async function renderPairing(runtime: CliCommandRuntime): Promise<string>
|
|
|
397
397
|
// lease change where a stable name exists. The verb re-derives the same origin
|
|
398
398
|
// and returns its honest posture, so this call is a persistence side effect.
|
|
399
399
|
ensurePublicBaseUrl(runtime.configManager);
|
|
400
|
-
const name = readOptionValue(runtime.cli.commandArgs, 'name') ?? defaultPairingTokenName();
|
|
401
|
-
// Mint through the canonical hand-off verb
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
400
|
+
const name = readOptionValue(runtime.cli.commandArgs, '--name') ?? defaultPairingTokenName();
|
|
401
|
+
// Mint through the canonical hand-off verb ON THE DAEMON: the deep link
|
|
402
|
+
// pairs a device to the daemon-hosted web app, and the offer set (push,
|
|
403
|
+
// relay, passkey) is the daemon's to filter to what it can satisfy. The
|
|
404
|
+
// local `services.gatewayMethods` catalog is deliberately empty in this
|
|
405
|
+
// surface (see runtime/services.ts), so invoking it answered
|
|
406
|
+
// "no internal handler" for every verb; the daemon verb caller is the
|
|
407
|
+
// canonical route and refuses honestly when no daemon is reachable.
|
|
408
|
+
const result = await services.daemonVerbs.invoke<PairingHandoffCreateResult>(
|
|
409
|
+
'pairing.handoff.create',
|
|
410
|
+
{ name, offers: [...PAIRING_HANDOFF_OFFER_KINDS] },
|
|
411
|
+
);
|
|
408
412
|
const link = result.deepLink ?? result.fragment;
|
|
409
413
|
const qr = renderQrToString(generateQrMatrix(link));
|
|
410
414
|
const lines: string[] = [
|
|
@@ -445,10 +449,7 @@ interface TailscaleGetResult {
|
|
|
445
449
|
*/
|
|
446
450
|
async function renderPairingTailscaleLines(services: RuntimeServices): Promise<string[]> {
|
|
447
451
|
try {
|
|
448
|
-
const ts =
|
|
449
|
-
body: {},
|
|
450
|
-
context: { principalId: 'admin', principalKind: 'user', admin: true },
|
|
451
|
-
})) as TailscaleGetResult;
|
|
452
|
+
const ts = await services.daemonVerbs.invoke<TailscaleGetResult>('tailscale.get', {});
|
|
452
453
|
if (!ts.available) return [];
|
|
453
454
|
if (ts.httpsUrl) return ['', `Encrypted access (Tailscale): ${ts.httpsUrl}`];
|
|
454
455
|
return ['', 'Tailscale detected: run it as a serve target for encrypted https access (see the pairing modal).'];
|
|
@@ -70,7 +70,7 @@ export function createBudgetBreachNotifier(deps: BudgetBreachNotifierDeps): Budg
|
|
|
70
70
|
const breached = computeBudgetBreach(sessionCost, budgetThresholdUsd);
|
|
71
71
|
|
|
72
72
|
if (!breached) {
|
|
73
|
-
notified = false; // dropped back under budget
|
|
73
|
+
notified = false; // dropped back under budget, can re-fire on the next crossing
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
if (notified) return false; // already alerted for this crossing
|
package/src/core/conversation.ts
CHANGED
|
@@ -364,7 +364,7 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
364
364
|
this.messageKindRegistry = new Map();
|
|
365
365
|
this.userReceiptIndices.clear();
|
|
366
366
|
this.streamingStartLine = -1;
|
|
367
|
-
this._displayFromMessageIndex = 0; // full reset
|
|
367
|
+
this._displayFromMessageIndex = 0; // full reset, show everything on next render
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
/**
|
|
@@ -26,7 +26,7 @@ export function registerGraphRuntimeCommands(registry: CommandRegistry): void {
|
|
|
26
26
|
async handler(args, ctx) {
|
|
27
27
|
const workstreamId = (args[0] ?? '').trim();
|
|
28
28
|
if (!workstreamId) {
|
|
29
|
-
ctx.print(
|
|
29
|
+
ctx.print("Usage: /graph <workstreamId> renders that workstream's task graph (nodes, edges, states, pool).");
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
const rpc = getOperatorRpc(ctx);
|
|
@@ -172,7 +172,7 @@ function renderChangeNames(result: ProfileWriteResultView): string[] {
|
|
|
172
172
|
* forgetting something that was not there reports that it was not there.
|
|
173
173
|
*
|
|
174
174
|
* `ok: true` with an empty change list is also not a success line. The daemon
|
|
175
|
-
* returns an empty disclosure when nothing changed, and inventing "Noted
|
|
175
|
+
* returns an empty disclosure when nothing changed, and inventing "Noted:"
|
|
176
176
|
* over the top of it would be the false receipt the design forbids.
|
|
177
177
|
*/
|
|
178
178
|
export function renderProfileWriteResult(result: ProfileWriteResultView): string {
|
|
@@ -330,7 +330,7 @@ function handleFallbackTest(
|
|
|
330
330
|
? node.explanation.rejections.map((r) => r.code).join(', ')
|
|
331
331
|
: '';
|
|
332
332
|
context.print(
|
|
333
|
-
` [${node.position}] ${node.providerId}/${node.modelId}
|
|
333
|
+
` [${node.position}] ${node.providerId}/${node.modelId}: ${reasons || 'unknown'}`,
|
|
334
334
|
);
|
|
335
335
|
}
|
|
336
336
|
if (rejected.length > 5) {
|
|
@@ -184,7 +184,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
184
184
|
for (const [id, content] of pending) {
|
|
185
185
|
const contentLines = content.split('\n');
|
|
186
186
|
const preview = contentLines[0].slice(0, 80);
|
|
187
|
-
lines.push(` ${id}
|
|
187
|
+
lines.push(` ${id} (${contentLines.length} lines): ${preview}${contentLines.length > 1 || preview.length < contentLines[0].length ? '…' : ''}`);
|
|
188
188
|
}
|
|
189
189
|
lines.push('', 'Full text: /pastes <id>');
|
|
190
190
|
ctx.print(lines.join('\n'));
|
|
@@ -220,7 +220,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
220
220
|
// every item here is a command, and picking one RUNS it, label
|
|
221
221
|
// the verb "Run" (matching the slash-command palette) instead of the
|
|
222
222
|
// generic "Select" default.
|
|
223
|
-
ctx.openSelection('Help
|
|
223
|
+
ctx.openSelection('Help: Commands', items, { allowSearch: true, primaryVerbLabel: 'Run' }, (result) => {
|
|
224
224
|
if (!result) return;
|
|
225
225
|
const name = result.item.id;
|
|
226
226
|
void (ctx.executeCommand?.(name, []) ?? registry.execute(name, [], ctx));
|
|
@@ -202,10 +202,10 @@ function formatItemMergeState(item: WorkItem): string {
|
|
|
202
202
|
* dependencies contributes nothing.
|
|
203
203
|
*/
|
|
204
204
|
function formatItemDependencyNote(item: WorkItem, ws: Workstream): string {
|
|
205
|
-
if (item.state === 'blocked-dependency' && item.blockedReason) return
|
|
205
|
+
if (item.state === 'blocked-dependency' && item.blockedReason) return `, ${item.blockedReason}`;
|
|
206
206
|
if (item.dependsOn && item.dependsOn.length > 0) {
|
|
207
207
|
const titleById = new Map(ws.items.map((i) => [i.id, i.title]));
|
|
208
|
-
return
|
|
208
|
+
return `, after: ${item.dependsOn.map((d) => titleById.get(d) ?? shortId(d)).join(', ')}`;
|
|
209
209
|
}
|
|
210
210
|
return '';
|
|
211
211
|
}
|
|
@@ -339,9 +339,9 @@ function renderWorkstreamStatus(ws: Workstream): string {
|
|
|
339
339
|
}
|
|
340
340
|
lines.push('Items:');
|
|
341
341
|
for (const item of ws.items) {
|
|
342
|
-
const mergeNote = isolated ?
|
|
342
|
+
const mergeNote = isolated ? `, ${formatItemMergeState(item)}` : '';
|
|
343
343
|
const depNote = formatItemDependencyNote(item, ws);
|
|
344
|
-
lines.push(` ${shortId(item.id)} [${item.state}] ${item.title}
|
|
344
|
+
lines.push(` ${shortId(item.id)} [${item.state}] ${item.title}, phase: ${item.currentPhaseId ?? '—'}${depNote}${mergeNote}`);
|
|
345
345
|
}
|
|
346
346
|
if (isolated) {
|
|
347
347
|
// Honest terminal-summary truth (never inferred from item.state alone,
|
|
@@ -249,7 +249,7 @@ export function handleGlobalShortcutToken(
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
case 'panel-ops': {
|
|
252
|
-
// Ctrl+O: TOGGLE the Fleet panel (
|
|
252
|
+
// Ctrl+O: TOGGLE the Fleet panel (same semantics as F2 above; see
|
|
253
253
|
// toggleFleetPanel's doc comment). The former Ops Control panel was
|
|
254
254
|
// retired to an 'ops-control' -> 'fleet' alias; rather than route
|
|
255
255
|
// through the now-aliased openOpsPanel callback (which opens without
|
package/src/input/handler.ts
CHANGED
|
@@ -287,7 +287,7 @@ export class InputHandler implements InputHandlerLike {
|
|
|
287
287
|
this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
|
|
288
288
|
this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager, uiServices.sessions.sessionBroker, getSharedHostedSessionRoster(), () => this.requestRender()); // third source: the daemon-hosted roster
|
|
289
289
|
this.profilePickerModal = new ProfilePickerModal(uiServices.shell.profileManager);
|
|
290
|
-
this.selectionQueue = attachSelectionModalQueue(this); // wires exitApp teardown too
|
|
290
|
+
this.selectionQueue = attachSelectionModalQueue(this); // wires exitApp teardown too, see selection-modal-queue.ts
|
|
291
291
|
this.initFeedContext();
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -249,7 +249,7 @@ export function buildDefaultModelStep(controller: OnboardingWizardControllerLike
|
|
|
249
249
|
kind: 'modelPicker',
|
|
250
250
|
id: 'default-model.primary-model',
|
|
251
251
|
label: 'Default provider + model',
|
|
252
|
-
hint:
|
|
252
|
+
hint: "Open the nested model picker for the shell's main routing target.",
|
|
253
253
|
target: 'main',
|
|
254
254
|
defaultSelection: {
|
|
255
255
|
providerId: normalizeText(routing?.primaryProviderId),
|
|
@@ -127,7 +127,7 @@ export function agentKindStyle(kind: AgentInspectorEntryKind, colors: Record<str
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// ---------------------------------------------------------------------------
|
|
130
|
-
// Inspector filter modes (ported from the merged agent-logs console
|
|
130
|
+
// Inspector filter modes (ported from the merged agent-logs console)
|
|
131
131
|
// ---------------------------------------------------------------------------
|
|
132
132
|
|
|
133
133
|
export type AgentInspectorFilterType = 'all' | 'assistant' | 'tool' | 'error';
|
|
@@ -256,7 +256,7 @@ export function buildWrfcCostSegments(
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
// ---------------------------------------------------------------------------
|
|
259
|
-
// Agent lifecycle event subscriptions (ported from agent-logs
|
|
259
|
+
// Agent lifecycle event subscriptions (ported from agent-logs)
|
|
260
260
|
// ---------------------------------------------------------------------------
|
|
261
261
|
|
|
262
262
|
export interface AgentActivityHooks {
|
|
@@ -123,7 +123,7 @@ export interface BuiltinPanelDeps {
|
|
|
123
123
|
workPlanStore?: import('@pellux/goodvibes-sdk/platform/workflow').WorkPlanStore;
|
|
124
124
|
/** Explicit UI-facing runtime services for agent/process/WRFC/remote panels and modals. */
|
|
125
125
|
uiServices?: UiRuntimeServices;
|
|
126
|
-
/** Shared plugin manager for plugin and security panels (widened past the read-only observer surface,
|
|
126
|
+
/** Shared plugin manager for plugin and security panels (widened past the read-only observer surface, so PluginsPanel can drive enable/disable/verify/lift-quarantine). */
|
|
127
127
|
pluginManager?: PluginManagerControls;
|
|
128
128
|
/** Shared hook dispatcher for the hooks control-room panel. */
|
|
129
129
|
hookDispatcher?: Pick<HookDispatcher, 'listHooks' | 'getChains'>;
|
package/src/panels/fleet-acts.ts
CHANGED
|
@@ -127,7 +127,7 @@ export class FleetActs {
|
|
|
127
127
|
const attention = fleetNodeAttention(node);
|
|
128
128
|
if (attention?.reason === 'pick') { void this.beginPick(node); return true; }
|
|
129
129
|
if (attention?.reason === 'conflict') { void this.resolveConflict(node); return true; }
|
|
130
|
-
return false; // not an act row
|
|
130
|
+
return false; // not an act row, let attach handle it
|
|
131
131
|
}
|
|
132
132
|
if (key === 'D') {
|
|
133
133
|
return this.discardWorktree(node);
|
|
@@ -364,7 +364,7 @@ export class FleetActs {
|
|
|
364
364
|
public discardWorktree(node: ProcessNode): boolean {
|
|
365
365
|
const item = workItemFromNode(node);
|
|
366
366
|
const path = item?.worktreePath;
|
|
367
|
-
if (!path) return false; // not a worktree-owning row
|
|
367
|
+
if (!path) return false; // not a worktree-owning row, let the key fall through
|
|
368
368
|
const gateway = this.requireGateway();
|
|
369
369
|
if (!gateway) return true;
|
|
370
370
|
this.deps.diffSurface.armConfirm({
|
|
@@ -419,7 +419,7 @@ export class FleetActs {
|
|
|
419
419
|
[selected ? ' ▸ ' : ' ', selected ? P.info : P.dim],
|
|
420
420
|
[`${cand.attemptIndex + 1}. `, P.label],
|
|
421
421
|
[cand.title, selected ? P.value : P.dim],
|
|
422
|
-
[
|
|
422
|
+
[`, ${files}, ${cost}`, P.dim],
|
|
423
423
|
]));
|
|
424
424
|
});
|
|
425
425
|
if (group.judgment?.proposedWinnerItemId) {
|
|
@@ -235,7 +235,7 @@ export class FleetSpawn {
|
|
|
235
235
|
lines.push(buildPanelLine(width, [
|
|
236
236
|
[selected ? ' ▸ ' : ' ', selected ? P.info : P.dim],
|
|
237
237
|
[agent.title, selected ? P.value : P.dim],
|
|
238
|
-
[
|
|
238
|
+
[`, ${agent.binaryPath}`, P.dim],
|
|
239
239
|
]));
|
|
240
240
|
});
|
|
241
241
|
} else {
|
|
@@ -246,7 +246,7 @@ export class FleetSpawn {
|
|
|
246
246
|
lines.push(buildPanelLine(width, [
|
|
247
247
|
[selected ? ' ▸ ' : ' ', selected ? P.info : P.dim],
|
|
248
248
|
[cand.label, selected ? P.value : P.dim],
|
|
249
|
-
[
|
|
249
|
+
[`, ${cand.path}`, P.dim],
|
|
250
250
|
]));
|
|
251
251
|
});
|
|
252
252
|
}
|
package/src/panels/git-panel.ts
CHANGED
|
@@ -366,7 +366,7 @@ export class GitPanel extends BasePanel {
|
|
|
366
366
|
}
|
|
367
367
|
if (isPanelSearchCommit(key)) {
|
|
368
368
|
const message = (this.commitMessage ?? '').trim();
|
|
369
|
-
if (!message) return true; // absorb
|
|
369
|
+
if (!message) return true; // absorb, require a non-empty message before confirming
|
|
370
370
|
this.confirm = { subject: { kind: 'commit', message }, label: message, verb: 'Commit' };
|
|
371
371
|
this.commitMessage = null;
|
|
372
372
|
this.markDirty();
|
|
@@ -255,7 +255,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
255
255
|
if (key === 'enter' || key === 'return') {
|
|
256
256
|
// Submit: call the auth API if a non-empty password was entered.
|
|
257
257
|
if (state.buffer.length === 0) {
|
|
258
|
-
return true; // no-op
|
|
258
|
+
return true; // no-op, require at least one character
|
|
259
259
|
}
|
|
260
260
|
const password = state.buffer;
|
|
261
261
|
const { kind, username, auth } = state;
|
|
@@ -124,10 +124,10 @@ class MarketplaceModalSurface implements ConfigModalSurface {
|
|
|
124
124
|
}
|
|
125
125
|
if (this.rows.length === 0) {
|
|
126
126
|
if (!this.deps.ecosystemPaths) {
|
|
127
|
-
rows.push(infoRow('empty:unwired',
|
|
127
|
+
rows.push(infoRow('empty:unwired', "Marketplace catalog roots aren't wired into this session, so there's nothing to read yet."));
|
|
128
128
|
} else {
|
|
129
129
|
rows.push(infoRow('empty:0', 'This is your local plugin, skill, hook-pack, and policy-pack catalog; not a remote store.'));
|
|
130
|
-
rows.push(infoRow('empty:1', '
|
|
130
|
+
rows.push(infoRow('empty:1', "It's empty because nothing has been published or imported into this workspace yet. Entries appear here once you publish a local component or import a bundle."));
|
|
131
131
|
}
|
|
132
132
|
rows.push(infoRow('empty:title', 'Populate it', { bold: true }));
|
|
133
133
|
rows.push(infoRow('empty:publish', '/marketplace publish <kind> <path> — publish local plugins/skills into the catalog', { dim: true }));
|
|
@@ -167,7 +167,7 @@ class MarketplaceModalSurface implements ConfigModalSurface {
|
|
|
167
167
|
if (recommendations.length > 0) {
|
|
168
168
|
rows.push(infoRow('rec:title', 'Recommended', { bold: true }));
|
|
169
169
|
for (const [i, rec] of recommendations.slice(0, 3).entries()) {
|
|
170
|
-
rows.push(infoRow(`rec:${i}`, `${rec.title}
|
|
170
|
+
rows.push(infoRow(`rec:${i}`, `${rec.title}: ${rec.command}`, { dim: true }));
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -605,7 +605,7 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
605
605
|
];
|
|
606
606
|
|
|
607
607
|
for (const [lbl, val, color] of rows) {
|
|
608
|
-
if (lines.length >= 8) break; // guard
|
|
608
|
+
if (lines.length >= 8) break; // guard, don't overflow the section
|
|
609
609
|
lines.push(buildStyledPanelLine(width, [
|
|
610
610
|
{ text: lbl, fg: C.label },
|
|
611
611
|
{ text: fmtTok(val), fg: color, bold: lbl.includes('Total') },
|
|
@@ -181,7 +181,7 @@ export class Compositor {
|
|
|
181
181
|
const borderFgForRow = (i: number): string => {
|
|
182
182
|
if (!hasPanel || !panel!.separator || !panelFocused) return PANEL_BORDER_DIM;
|
|
183
183
|
if (!hasBottomPane) return panel!.topFocused ? PANEL_FOCUS_ACCENT : PANEL_BORDER_DIM;
|
|
184
|
-
if (i === 0) return PANEL_FOCUS_ACCENT; // workspace bar
|
|
184
|
+
if (i === 0) return PANEL_FOCUS_ACCENT; // workspace bar, panel is focused
|
|
185
185
|
if (i <= topPaneHeight) return panel!.topFocused ? PANEL_FOCUS_ACCENT : PANEL_BORDER_DIM;
|
|
186
186
|
return panel!.bottomFocused ? PANEL_FOCUS_ACCENT : PANEL_BORDER_DIM;
|
|
187
187
|
};
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* surfacing as an unhandled rejection that killed whatever bootstrap step was
|
|
10
10
|
* mid-flight. The firing path has to be a callback some installer in that
|
|
11
11
|
* window invokes synchronously with the rejection crossing a promise boundary
|
|
12
|
-
* on the way out
|
|
13
|
-
* can interleave into it
|
|
12
|
+
* on the way out. The window contains no await, so no timer or scan callback
|
|
13
|
+
* can interleave into it, and this indirection removes the entire class
|
|
14
14
|
* rather than the one caller: a closure that fires before {@link
|
|
15
15
|
* DeferredRender.set} runs gets a no-op, and main()'s unconditional first
|
|
16
16
|
* paint after wiring covers everything deferred.
|
|
@@ -708,7 +708,7 @@ export async function initializeBootstrapCore(
|
|
|
708
708
|
sandboxExecAskDepsFromRuntime(configManager, featureFlags),
|
|
709
709
|
(request) => services.requestApproval({ request }),
|
|
710
710
|
),
|
|
711
|
-
() => trustPromptRef.requestTrustDecision(), // indirection through the ref, not bound early
|
|
711
|
+
() => trustPromptRef.requestTrustDecision(), // indirection through the ref, not bound early. main.ts patches the real impl in later
|
|
712
712
|
),
|
|
713
713
|
createPermissionConfigReader(configManager),
|
|
714
714
|
policyRuntimeState,
|
|
@@ -100,7 +100,7 @@ function acquireLock(lp: string): boolean {
|
|
|
100
100
|
if (Date.now() - st.mtimeMs >= LOCK_STALE_MS) {
|
|
101
101
|
try { unlinkSync(lp); } catch { /* another process may have beaten us */ }
|
|
102
102
|
}
|
|
103
|
-
} catch { /* lockfile does not exist
|
|
103
|
+
} catch { /* lockfile does not exist, expected happy path */ }
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
106
|
// 'wx' ≡ O_CREAT | O_EXCL | O_WRONLY, fails atomically if file exists.
|
|
@@ -157,7 +157,7 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
|
|
|
157
157
|
// Recognized kinds (auth, rate-limit, network...) keep their specific
|
|
158
158
|
// line. A generic rejection used to be captioned "Provider error",
|
|
159
159
|
// which sent a startup UI crash out dressed as a model-backend
|
|
160
|
-
// problem
|
|
160
|
+
// problem. The reverse lie matters too: the classifier has no
|
|
161
161
|
// 5xx rule, so a provider outage also classifies generic, and calling
|
|
162
162
|
// THAT a GoodVibes bug blames us for OpenAI's downtime. The
|
|
163
163
|
// discriminator is whether the reason carries provider markers
|
|
@@ -305,5 +305,5 @@ export interface RuntimeServices {
|
|
|
305
305
|
* rather than preserved.
|
|
306
306
|
*/
|
|
307
307
|
cancelHostedAgentRuns(): number;
|
|
308
|
-
dispose(): void; // Stop every poller this graph started; best-effort, total, idempotent. This surface owns its graph
|
|
308
|
+
dispose(): void; // Stop every poller this graph started; best-effort, total, idempotent. This surface owns its graph, see disposal-wiring.ts.
|
|
309
309
|
}
|
package/src/runtime/services.ts
CHANGED
|
@@ -453,7 +453,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
453
453
|
configManager, secretsManager, providerRegistry, memoryDbPath, codeIndexDbPath: codeIndexDbPath(workingDirectory, GOODVIBES_TUI_SURFACE_ROOT), surface, shellPaths,
|
|
454
454
|
...(options.currentSessionId ? { currentSessionId: options.currentSessionId } : {}),
|
|
455
455
|
});
|
|
456
|
-
const { processRegistry } = createFleetServices({ // Shared archive-aware fleet registry
|
|
456
|
+
const { processRegistry } = createFleetServices({ // Shared archive-aware fleet registry, see fleet-services.ts
|
|
457
457
|
agentManager, wrfcController,
|
|
458
458
|
orchestrationEngine,
|
|
459
459
|
codeIndexService: codeIndexStore,
|
|
@@ -48,7 +48,7 @@ type AgentTaskArgs = {
|
|
|
48
48
|
* Emitted whenever the guard changes the effective routing decision:
|
|
49
49
|
* - 'spawn-forced-wrfc' , implementation-like spawn promoted to WRFC
|
|
50
50
|
* - 'spawn-suppressed-wrfc' , spawn judged read-only; WRFC suppressed
|
|
51
|
-
* - 'batch-collapsed-to-wrfc'
|
|
51
|
+
* - 'batch-collapsed-to-wrfc', batch-spawn collapsed into a single WRFC owner chain
|
|
52
52
|
*/
|
|
53
53
|
export type WrfcGuardTraceKind =
|
|
54
54
|
| 'spawn-forced-wrfc'
|