@pellux/goodvibes-tui 0.19.16 → 0.19.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.16",
3
+ "version": "0.19.20",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -90,7 +90,7 @@
90
90
  "@anthropic-ai/vertex-sdk": "^0.16.0",
91
91
  "@ast-grep/napi": "^0.42.0",
92
92
  "@aws/bedrock-token-generator": "^1.1.0",
93
- "@pellux/goodvibes-sdk": "0.21.27",
93
+ "@pellux/goodvibes-sdk": "0.21.36",
94
94
  "bash-language-server": "^5.6.0",
95
95
  "fuse.js": "^7.1.0",
96
96
  "graphql": "^16.13.2",
package/src/daemon/cli.ts CHANGED
@@ -12,11 +12,13 @@ import { GlobalNetworkTransportInstaller } from '@pellux/goodvibes-sdk/platform/
12
12
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
13
13
  import {
14
14
  getOrCreateCompanionToken,
15
+ pruneStaleOperatorTokens,
15
16
  buildCompanionConnectionInfo,
16
17
  encodeConnectionPayload,
17
18
  formatConnectionBlock,
18
19
  } from '@pellux/goodvibes-sdk/platform/pairing/index';
19
20
  import { generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing/qr-generator';
21
+ import { workspaceOperatorTokenCandidates } from '../runtime/operator-token-cleanup.ts';
20
22
  import {
21
23
  scan,
22
24
  loadPersistedProviders,
@@ -147,7 +149,20 @@ async function main(): Promise<void> {
147
149
  const { daemonToken, httpToken } = readDaemonCliTokens(process.env);
148
150
 
149
151
  // If no explicit daemon token is set, use the companion token so mobile apps can connect.
150
- const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir: join(homeDirectory, '.goodvibes', 'daemon') });
152
+ const daemonHomeDir = join(homeDirectory, '.goodvibes', 'daemon');
153
+ const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
154
+ // F3 resolution (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
155
+ // token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
156
+ const prune = pruneStaleOperatorTokens({
157
+ daemonHomeDir,
158
+ candidatePaths: workspaceOperatorTokenCandidates(workingDir),
159
+ });
160
+ if (prune.prunedPaths.length > 0) {
161
+ logger.info('daemon: pruned stale operator-token files', { count: prune.prunedPaths.length, paths: prune.prunedPaths });
162
+ }
163
+ if (prune.failedPaths.length > 0) {
164
+ logger.warn('daemon: failed to prune stale operator-token files (permission/race)', { count: prune.failedPaths.length, paths: prune.failedPaths });
165
+ }
151
166
  const effectiveDaemonToken = daemonToken ?? companionTokenRecord.token;
152
167
  const effectiveHttpToken = httpToken ?? effectiveDaemonToken;
153
168
 
@@ -75,14 +75,16 @@ function truncateJson(val: unknown, maxLen = 120): string {
75
75
  }
76
76
 
77
77
  function summarizeResult(result: unknown): string | undefined {
78
+ // SDK OBS-05 (0.21.31+): TOOL_SUCCEEDED/TOOL_FAILED.result is a ToolResultSummary
79
+ // { kind: 'text' | 'json' | 'binary' | 'error' | 'empty'; byteSize: number; preview?: string }.
78
80
  if (!result || typeof result !== 'object') return undefined;
79
81
  const record = result as Record<string, unknown>;
80
- if (typeof record.output === 'string' && record.output.trim()) {
81
- const compact = record.output.replace(/\s+/g, ' ').trim();
82
+ if (typeof record.preview === 'string' && record.preview.trim()) {
83
+ const compact = record.preview.replace(/\s+/g, ' ').trim();
82
84
  return compact.length > 72 ? `${compact.slice(0, 69)}\u2026` : compact;
83
85
  }
84
- if (typeof record.error === 'string' && record.error.trim()) {
85
- return record.error;
86
+ if (typeof record.kind === 'string' && typeof record.byteSize === 'number') {
87
+ return `${record.kind} (${record.byteSize}B)`;
86
88
  }
87
89
  return undefined;
88
90
  }
@@ -382,17 +384,16 @@ export class ToolInspectorPanel extends BasePanel {
382
384
  this.markDirty();
383
385
  }));
384
386
 
387
+ // NOTE: After SDK OBS-05 (0.21.31), TOOL_SUCCEEDED/TOOL_FAILED.result is a ToolResultSummary
388
+ // ({ kind, byteSize, preview? }) rather than the raw ToolResult object. The previous
389
+ // `_policyAudit` extraction is no longer reachable via this event — policy audit metadata
390
+ // must be sourced from a different channel (approval broker / tool result store) if the
391
+ // Tool Inspector is to display it in future.
385
392
  this.unsubs.push(this.toolEvents.on('TOOL_SUCCEEDED', (data) => {
386
393
  const rec = this.records.findLast(r => r.callId === data.callId);
387
394
  if (rec) {
388
395
  rec.endMs = Date.now();
389
396
  rec.result = data.result;
390
- if (data.result && typeof data.result === 'object') {
391
- const audit = (data.result as { _policyAudit?: { actionTaken?: string; spillBackend?: string; policyId?: string } })._policyAudit;
392
- rec.policyAction = audit?.actionTaken;
393
- rec.spillBackend = audit?.spillBackend;
394
- rec.outputClass = audit?.policyId ?? rec.outputClass;
395
- }
396
397
  rec.resultSummary = summarizeResult(data.result);
397
398
  }
398
399
  this.markDirty();
@@ -404,12 +405,6 @@ export class ToolInspectorPanel extends BasePanel {
404
405
  rec.endMs = Date.now();
405
406
  rec.result = data.result;
406
407
  rec.error = data.error;
407
- if (data.result && typeof data.result === 'object') {
408
- const audit = (data.result as { _policyAudit?: { actionTaken?: string; spillBackend?: string; policyId?: string } })._policyAudit;
409
- rec.policyAction = audit?.actionTaken;
410
- rec.spillBackend = audit?.spillBackend;
411
- rec.outputClass = audit?.policyId ?? rec.outputClass;
412
- }
413
408
  rec.resultSummary = summarizeResult(data.result) ?? data.error;
414
409
  }
415
410
  this.markDirty();
@@ -9,6 +9,7 @@
9
9
  * - main.ts: terminal setup, render loop, stdin/stdout handlers
10
10
  * - lifecycle.ts: save/shutdown helpers
11
11
  */
12
+ import { join } from 'node:path';
12
13
  import { Orchestrator } from '../core/orchestrator.ts';
13
14
  import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp/manager';
14
15
  import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers/tier-prompts';
@@ -35,7 +36,8 @@ import {
35
36
  import { startBackgroundProviderRegistration } from '@pellux/goodvibes-sdk/platform/runtime/bootstrap-background';
36
37
  import { restoreSavedModel } from '@pellux/goodvibes-sdk/platform/runtime/bootstrap-helpers';
37
38
  import { startExternalServices, type ExternalServicesHandle } from '@pellux/goodvibes-sdk/platform/runtime/bootstrap-services';
38
- import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing/companion-token';
39
+ import { getOrCreateCompanionToken, pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing/companion-token';
40
+ import { workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
39
41
  import type { UiRuntimeServices } from './ui-services.ts';
40
42
  import { createDeferredStartupCoordinator } from '@pellux/goodvibes-sdk/platform/runtime/deferred-startup';
41
43
  import { initializeBootstrapCore } from './bootstrap-core.ts';
@@ -330,7 +332,23 @@ export async function bootstrapRuntime(
330
332
  // Register the persistent companion-pairing token as the daemon's shared
331
333
  // bearer, so tokens scanned from the /qrcode panel's QR actually
332
334
  // authenticate against the embedded daemon this surface starts.
333
- const companionTokenRecord = getOrCreateCompanionToken('tui');
335
+ const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
336
+ const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
337
+ // F3 resolution (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
338
+ // token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
339
+ // The prune is best-effort — it silently skips missing files, no-ops when tokens
340
+ // already match, and records un-deletable candidates in `failedPaths` for logging.
341
+ // See `pruneStaleOperatorTokens` in the SDK for semantics.
342
+ const prune = pruneStaleOperatorTokens({
343
+ daemonHomeDir,
344
+ candidatePaths: workspaceOperatorTokenCandidates(services.workingDirectory),
345
+ });
346
+ if (prune.prunedPaths.length > 0) {
347
+ logger.info(`[bootstrap] Pruned ${prune.prunedPaths.length} stale operator-token file(s): ${prune.prunedPaths.join(', ')}`);
348
+ }
349
+ if (prune.failedPaths.length > 0) {
350
+ logger.warn(`[bootstrap] Failed to prune ${prune.failedPaths.length} stale operator-token file(s) (permission/race): ${prune.failedPaths.join(', ')}`);
351
+ }
334
352
  externalServicesPromise = startExternalServices(
335
353
  configManager,
336
354
  runtimeBus,
@@ -0,0 +1,28 @@
1
+ /**
2
+ * operator-token-cleanup.ts
3
+ *
4
+ * Shared helper that enumerates the legacy workspace-scoped `operator-tokens.json`
5
+ * locations the TUI has written at various pre-0.21.28 versions. Used by both the
6
+ * in-process bootstrap path (`src/runtime/bootstrap.ts`) and the standalone daemon
7
+ * CLI (`src/daemon/cli.ts`) so F3 (stale-token pruning) has a single source of
8
+ * truth for where to look.
9
+ *
10
+ * Adding a new legacy location: append to `workspaceOperatorTokenCandidates` and
11
+ * the new path will be inspected on the next daemon boot.
12
+ */
13
+
14
+ import { join } from 'node:path';
15
+
16
+ /**
17
+ * Return the list of absolute operator-tokens.json paths the TUI may have written
18
+ * at legacy (pre-0.21.28) workspace-scoped locations under `workingDirectory`.
19
+ *
20
+ * The canonical, current-SDK location is `<daemonHomeDir>/operator-tokens.json`;
21
+ * this helper is strictly for legacy-cleanup candidates.
22
+ */
23
+ export function workspaceOperatorTokenCandidates(workingDirectory: string): readonly string[] {
24
+ return [
25
+ join(workingDirectory, '.goodvibes', 'operator-tokens.json'),
26
+ join(workingDirectory, '.goodvibes', 'tui', 'operator-tokens.json'),
27
+ ];
28
+ }
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.19.16';
9
+ let _version = '0.19.20';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;