@pellux/goodvibes-agent 1.5.7 → 1.5.9
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 +16 -3
- package/README.md +1 -1
- package/dist/package/main.js +309 -150
- package/package.json +1 -1
- package/src/agent/memory-prompt.ts +3 -3
- package/src/agent/operator-actions.ts +1 -1
- package/src/agent/prompt-context-receipts.ts +3 -3
- package/src/agent/session-registration.ts +1 -1
- package/src/agent/vibe-file.ts +5 -5
- package/src/audio/player.ts +91 -5
- package/src/audio/spoken-turn-controller.ts +281 -29
- package/src/audio/spoken-turn-wiring.ts +13 -2
- package/src/cli/local-library-command-shared.ts +1 -1
- package/src/cli/memory-command.ts +9 -6
- package/src/cli/resume-relaunch-notice.ts +1 -1
- package/src/cli/tui-startup.ts +1 -1
- package/src/config/credential-status.ts +1 -1
- package/src/config/index.ts +1 -1
- package/src/config/secrets.ts +1 -1
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/system-message-noise.ts +3 -3
- package/src/core/thinking-overlay.ts +1 -1
- package/src/input/agent-workspace-basic-command-editor-submission.ts +1 -1
- package/src/input/agent-workspace-basic-command-editors.ts +1 -1
- package/src/input/agent-workspace-calendar-connect-editor.ts +1 -1
- package/src/input/agent-workspace-calendar-oauth-editor.ts +1 -1
- package/src/input/agent-workspace-calendar-subscribe-editor.ts +1 -1
- package/src/input/agent-workspace-command-editor-engine.ts +2 -2
- package/src/input/agent-workspace-direct-editor-submission.ts +1 -1
- package/src/input/agent-workspace-email-connect-editor.ts +1 -1
- package/src/input/agent-workspace-live-counters.ts +1 -1
- package/src/input/agent-workspace-settings.ts +1 -1
- package/src/input/agent-workspace-snapshot-builders.ts +2 -2
- package/src/input/agent-workspace-snapshot-config.ts +1 -1
- package/src/input/agent-workspace-snapshot.ts +9 -9
- package/src/input/agent-workspace-types.ts +4 -4
- package/src/input/agent-workspace.ts +1 -1
- package/src/input/commands/calendar-connect-runtime.ts +1 -1
- package/src/input/commands/calendar-subscription-runtime.ts +2 -2
- package/src/input/commands/operator-actions-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/feed-context-factory.ts +4 -4
- package/src/input/handler-feed.ts +9 -9
- package/src/input/handler.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +3 -3
- package/src/input/settings-modal.ts +1 -1
- package/src/main.ts +28 -33
- package/src/renderer/agent-workspace-context-lines.ts +1 -1
- package/src/renderer/startup-theme-probe.ts +1 -1
- package/src/renderer/status-glyphs.ts +2 -2
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/terminal-escapes.ts +3 -2
- package/src/renderer/theme-mode-config.ts +2 -2
- package/src/renderer/theme.ts +4 -4
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/ui-factory.ts +1 -1
- package/src/renderer/ui-primitives.ts +2 -2
- package/src/runtime/bootstrap-core.ts +2 -2
- package/src/runtime/bootstrap.ts +3 -3
- package/src/runtime/calendar-boot-refresh.ts +1 -1
- package/src/runtime/lan-scan-consent.ts +1 -1
- package/src/runtime/services.ts +7 -7
- package/src/runtime/terminal-output-guard.ts +1 -1
- package/src/runtime/ui-services.ts +1 -1
- package/src/runtime/unhandled-rejection-guard.ts +41 -0
- package/src/shell/agent-workspace-fullscreen.ts +1 -1
- package/src/shell/terminal-focus-mode.ts +9 -9
- package/src/shell/ui-openers.ts +1 -1
- package/src/tools/agent-harness-operator-methods.ts +1 -1
- package/src/tools/agent-harness-personal-ops-discovery.ts +2 -2
- package/src/tools/agent-harness-personal-ops-lanes.ts +1 -1
- package/src/tools/agent-harness-personal-ops-types.ts +1 -1
- package/src/tools/agent-harness-prompt-context.ts +3 -3
- package/src/tools/agent-local-registry-memory.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Calendar connect card
|
|
2
|
+
* Calendar connect card — promotes the "Calendar workflows" workspace
|
|
3
3
|
* card from a dead guidance card into a real, dispatchable action.
|
|
4
4
|
*
|
|
5
5
|
* GROUNDED: unlike email, there is no external calendar account/CalDAV
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Calendar OAuth advanced-credentials wizard
|
|
2
|
+
* Calendar OAuth advanced-credentials wizard.
|
|
3
3
|
*
|
|
4
4
|
* The DEFAULT connect experience needs no card at all: `/calendar connect google`
|
|
5
5
|
* (or `outlook`) uses the bundled project client id + PKCE and opens the browser.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Calendar subscribe wizard
|
|
2
|
+
* Calendar subscribe wizard — the "Calendar workflows" card's real
|
|
3
3
|
* connect flow. Subscribes to an external calendar by its iCalendar feed URL
|
|
4
4
|
* (Google "secret address", Outlook published .ics, or any .ics URL), READ-ONLY.
|
|
5
5
|
*
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// one place. Each domain keeps its own field content and command-building logic
|
|
10
10
|
// (real product value, not boilerplate) as entries in a `Record<Kind, ...>` table.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
// identified by
|
|
12
|
+
// Consolidates the ~26 near-identical editor+submission file pairs
|
|
13
|
+
// identified by an earlier design ruling into this engine + per-domain data tables.
|
|
14
14
|
import type { AgentWorkspaceActionResult, AgentWorkspaceEditorField, AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
15
15
|
import { quoteSlashCommandArg } from './slash-command-parser.ts';
|
|
16
16
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* generic command-editor dispatch pipeline (agent-workspace-command-editor.ts).
|
|
5
5
|
* Centralizing the dispatch here keeps AgentWorkspace.submitLocalEditor()
|
|
6
6
|
* under the architecture line cap as new direct-action editor kinds are
|
|
7
|
-
* added (
|
|
7
|
+
* added (email-connect-wizard alongside the existing
|
|
8
8
|
* subscription-login-start/finish/logout kinds).
|
|
9
9
|
*/
|
|
10
10
|
import type { CommandContext } from './command-registry.ts';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Email connect wizard
|
|
2
|
+
* Email connect wizard — promotes the "Inbox workflows" workspace
|
|
3
3
|
* card from a dead guidance card into a real, stepped connect flow.
|
|
4
4
|
*
|
|
5
5
|
* Security: the raw password NEVER travels through the generic slash-command
|
|
@@ -10,7 +10,7 @@ export interface SyncAgentWorkspaceLiveCountersOptions {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Quiet, render-path refresh of the two counters
|
|
13
|
+
* Quiet, render-path refresh of the two counters a dogfood finding
|
|
14
14
|
* named as stale: the memory count and the routine counts (incl. each
|
|
15
15
|
* routine's live start count). Call this once per repaint, before rendering,
|
|
16
16
|
* so an external disk mutation (another shell deleting a memory, a CLI
|
|
@@ -330,7 +330,7 @@ export function agentWorkspaceSettingSchema(context: CommandContext | null, key:
|
|
|
330
330
|
?.getSchema()
|
|
331
331
|
.find((setting) => setting.key === key) ?? null;
|
|
332
332
|
if (fromSchema) return fromSchema;
|
|
333
|
-
// display.themeMode is a TUI-local synthetic setting
|
|
333
|
+
// display.themeMode is a TUI-local synthetic setting that never
|
|
334
334
|
// joined the SDK ConfigKey schema — the classic settings-modal injects the
|
|
335
335
|
// same descriptor into its own groups map rather than the schema. Mirror
|
|
336
336
|
// that fallback here so the workspace surface can discover and cycle it too.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Sub-builders extracted out of the single ~516-line `buildAgentWorkspaceRuntimeSnapshot`
|
|
2
|
-
// function in agent-workspace-snapshot.ts (
|
|
2
|
+
// function in agent-workspace-snapshot.ts (per the design ruling's
|
|
3
3
|
// giant-single-function-assembler verdict: "splitting into cohesive sub-builders...
|
|
4
4
|
// would reduce single-function size without changing behavior"). Each function here
|
|
5
5
|
// is the verbatim body of one of that function's local IIFEs or local variable blocks,
|
|
@@ -273,7 +273,7 @@ export function buildAgentWorkspaceCompanionAccessSnapshot(context: CommandConte
|
|
|
273
273
|
|
|
274
274
|
/**
|
|
275
275
|
* Honest email-connect status for the inbox connect wizard's entry state
|
|
276
|
-
*
|
|
276
|
+
* — config validation only, no network I/O. Best-effort: never
|
|
277
277
|
* throws, returns null on any read failure.
|
|
278
278
|
*/
|
|
279
279
|
export function buildAgentWorkspaceEmailConnectStatus(context: CommandContext): AgentWorkspaceEmailConnectStatus | null {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Small config-read helpers shared by agent-workspace-snapshot.ts and
|
|
2
|
-
// agent-workspace-snapshot-builders.ts (
|
|
2
|
+
// agent-workspace-snapshot-builders.ts (split out of the runtime-snapshot giant
|
|
3
3
|
// assembler). Each never throws: config reads fall back to the given default on any
|
|
4
4
|
// error, matching the original inline behavior exactly.
|
|
5
5
|
import type { CommandContext } from './command-registry.ts';
|
|
@@ -36,8 +36,8 @@ import {
|
|
|
36
36
|
} from './agent-workspace-snapshot-builders.ts';
|
|
37
37
|
import type { AgentWorkspaceLocalLibraryItem, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
|
|
38
38
|
|
|
39
|
-
// Re-exported so this module's public surface matches the pre-split file (
|
|
40
|
-
//
|
|
39
|
+
// Re-exported so this module's public surface matches the pre-split file (this
|
|
40
|
+
// builder was originally exported here, before a later split moved its body to
|
|
41
41
|
// agent-workspace-snapshot-builders.ts).
|
|
42
42
|
export { buildAgentWorkspaceEmailConnectStatus } from './agent-workspace-snapshot-builders.ts';
|
|
43
43
|
|
|
@@ -65,16 +65,16 @@ export interface AgentWorkspaceLiveMemoryCounters {
|
|
|
65
65
|
* mid-call) rather than swallowing it — callers that need a best-effort,
|
|
66
66
|
* never-throws default (the full runtime snapshot builder below) should use
|
|
67
67
|
* buildAgentWorkspaceMemorySnapshot instead. The render-path live-counter
|
|
68
|
-
* refresh (AgentWorkspace.syncLiveCountersForRender
|
|
68
|
+
* refresh (AgentWorkspace.syncLiveCountersForRender) calls this
|
|
69
69
|
* directly so a genuine read failure can be surfaced as "stale" instead of
|
|
70
70
|
* being silently rewritten to a fabricated 0.
|
|
71
71
|
*
|
|
72
|
-
* NOTE (
|
|
72
|
+
* NOTE (observability flag): these live process/state counters are
|
|
73
73
|
* flagged as observability-shaped content whose eventual surface home is the
|
|
74
74
|
* fleet/observability layer, not this admin-console snapshot builder. Nothing
|
|
75
75
|
* moves today (no observability layer exists yet); this function is kept
|
|
76
76
|
* intact and separate rather than folded into the sibling sub-builders in
|
|
77
|
-
* agent-workspace-snapshot-builders.ts extracted alongside it
|
|
77
|
+
* agent-workspace-snapshot-builders.ts extracted alongside it.
|
|
78
78
|
*/
|
|
79
79
|
export function readLiveAgentMemoryCounters(context: CommandContext): AgentWorkspaceLiveMemoryCounters {
|
|
80
80
|
const memory = context.clients?.agentKnowledgeApi?.memory;
|
|
@@ -85,7 +85,7 @@ export function readLiveAgentMemoryCounters(context: CommandContext): AgentWorks
|
|
|
85
85
|
reviewQueueCount: memory.reviewQueue(100).length,
|
|
86
86
|
promptActiveCount: records.filter(isPromptActiveMemory).length,
|
|
87
87
|
// Each item carries the honest, per-record eligibility reason straight from
|
|
88
|
-
// describeMemoryPromptEligibility
|
|
88
|
+
// describeMemoryPromptEligibility — the same wording source
|
|
89
89
|
// prompt-context-receipts.ts and agent-harness-prompt-context.ts use for prompt
|
|
90
90
|
// recall. No locally invented "not reviewed"/"outside prompt limit" paraphrase here.
|
|
91
91
|
items: records.map((record) => ({
|
|
@@ -120,9 +120,9 @@ export interface AgentWorkspaceLiveRoutineCounters {
|
|
|
120
120
|
* if the store read fails (e.g. a corrupt/unreadable routines.json —
|
|
121
121
|
* AgentRoutineRegistry.snapshot() itself throws in that case) rather than
|
|
122
122
|
* swallowing it; see readLiveAgentMemoryCounters above for why the
|
|
123
|
-
* render-path live-counter refresh
|
|
123
|
+
* render-path live-counter refresh wants that.
|
|
124
124
|
*
|
|
125
|
-
* NOTE (
|
|
125
|
+
* NOTE (observability flag): see readLiveAgentMemoryCounters above —
|
|
126
126
|
* the same flag applies to these routine counters.
|
|
127
127
|
*/
|
|
128
128
|
export function readLiveAgentRoutineCounters(context: CommandContext): AgentWorkspaceLiveRoutineCounters {
|
|
@@ -150,7 +150,7 @@ export function buildAgentWorkspaceRoutineCounters(context: CommandContext): Age
|
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
152
|
* Assembles the full Agent workspace runtime snapshot. Composes the
|
|
153
|
-
* sub-builders in agent-workspace-snapshot-builders.ts (
|
|
153
|
+
* sub-builders in agent-workspace-snapshot-builders.ts (split out of what
|
|
154
154
|
* was previously one ~516-line function) plus the two live-counter builders
|
|
155
155
|
* above, then maps everything onto the AgentWorkspaceRuntimeSnapshot shape.
|
|
156
156
|
*/
|
|
@@ -370,7 +370,7 @@ export interface AgentWorkspaceLocalLibraryItem {
|
|
|
370
370
|
readonly missingRequirementCount?: number;
|
|
371
371
|
readonly missingRequirements?: readonly string[];
|
|
372
372
|
readonly startCount?: number;
|
|
373
|
-
/** Memory-only
|
|
373
|
+
/** Memory-only: whether this record currently clears the prompt-injection
|
|
374
374
|
* recall floor, per describeMemoryPromptEligibility. */
|
|
375
375
|
readonly promptEligible?: boolean;
|
|
376
376
|
/** Memory-only: the honest, per-record reason from describeMemoryPromptEligibility —
|
|
@@ -640,7 +640,7 @@ export interface AgentWorkspaceCompanionAccessSummary {
|
|
|
640
640
|
readonly nextStep: string;
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
-
/** Honest, no-I/O email-connect status (config validation only) for the inbox connect card/wizard
|
|
643
|
+
/** Honest, no-I/O email-connect status (config validation only) for the inbox connect card/wizard. */
|
|
644
644
|
export interface AgentWorkspaceEmailConnectStatus {
|
|
645
645
|
readonly connected: boolean;
|
|
646
646
|
readonly username: string;
|
|
@@ -785,7 +785,7 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
785
785
|
readonly warnings: readonly string[];
|
|
786
786
|
/**
|
|
787
787
|
* True when the most recent render-path attempt to re-derive the live
|
|
788
|
-
* counters (memory count, routine counts + start counts
|
|
788
|
+
* counters (memory count, routine counts + start counts) failed
|
|
789
789
|
* and the previous values below had to be kept. A fresh
|
|
790
790
|
* buildAgentWorkspaceRuntimeSnapshot() call is never stale (it just read
|
|
791
791
|
* everything); this only flips true from
|
|
@@ -794,7 +794,7 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
794
794
|
* instead of asserting a number the disk might already contradict.
|
|
795
795
|
*/
|
|
796
796
|
readonly liveCountersStale: boolean;
|
|
797
|
-
/** Honest email-connect status for the connect wizard's entry state
|
|
797
|
+
/** Honest email-connect status for the connect wizard's entry state. Null when unavailable in this runtime. */
|
|
798
798
|
readonly emailConnectStatus: AgentWorkspaceEmailConnectStatus | null;
|
|
799
799
|
readonly calendarOAuthConfigStatus: { readonly google: boolean; readonly microsoft: boolean } | null; // F1c: per-provider client-id-configured state for the advanced cards
|
|
800
800
|
}
|
|
@@ -219,7 +219,7 @@ export class AgentWorkspace {
|
|
|
219
219
|
this.lastActionResult = { kind: 'refreshed', title: 'Runtime context refreshed', detail: 'Provider, model, session, local memory, runtime endpoint, and Agent knowledge route posture were re-read from the live command context.' };
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
/**
|
|
222
|
+
/** Render-path live counter mirror (see syncAgentWorkspaceLiveCounters). */ syncLiveCountersForRender(): void { syncAgentWorkspaceLiveCounters({ context: this.context, runtimeSnapshot: this.runtimeSnapshot, setRuntimeSnapshot: (snapshot) => { this.runtimeSnapshot = snapshot; }, clampSelection: () => this.clampSelection() }); }
|
|
223
223
|
|
|
224
224
|
cancelLocalEditor(): void {
|
|
225
225
|
if (!this.localEditor) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* calendar-connect-runtime.ts — the /calendar connect|disconnect|accounts subcommands
|
|
3
|
-
* and the provider-event merge for /calendar upcoming
|
|
3
|
+
* and the provider-event merge for /calendar upcoming.
|
|
4
4
|
*
|
|
5
5
|
* The OAuth network dance lives here (not in the workspace card) because a command
|
|
6
6
|
* handler can print the authorization URL / device user-code IMMEDIATELY, then await
|
|
@@ -11,8 +11,8 @@ import { parseAgentLocalLibraryArgs } from './agent-local-library-args.ts';
|
|
|
11
11
|
import { requireShellPaths } from './runtime-services.ts';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* External-calendar SUBSCRIPTION verbs for /calendar — the no-OAuth read path
|
|
15
|
-
*
|
|
14
|
+
* External-calendar SUBSCRIPTION verbs for /calendar — the no-OAuth read path.
|
|
15
|
+
* Subscriptions are iCalendar feeds (Google secret address, Outlook
|
|
16
16
|
* published .ics, or any .ics URL). The parse/RRULE/fetch-status engine is the
|
|
17
17
|
* SDK's platform/calendar; this file is the /calendar command surface over the
|
|
18
18
|
* agent-side CalendarSubscriptionRegistry.
|
|
@@ -93,7 +93,7 @@ function automationActionFromParts(scope: string, verb: string): {
|
|
|
93
93
|
readonly targetField: 'jobId' | 'runId' | 'scheduleId';
|
|
94
94
|
} | null {
|
|
95
95
|
if ((scope === 'job' || scope === 'jobs') && verb === 'run') return { action: 'automation.jobs.run', targetField: 'jobId' };
|
|
96
|
-
//
|
|
96
|
+
// automation.jobs.pause/resume retired (redundant with
|
|
97
97
|
// disable/enable) — the user-facing "pause"/"resume" verb is unchanged,
|
|
98
98
|
// it now maps onto the canonical disable/enable wire actions.
|
|
99
99
|
if ((scope === 'job' || scope === 'jobs') && verb === 'pause') return { action: 'automation.jobs.disable', targetField: 'jobId' };
|
|
@@ -254,7 +254,7 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
254
254
|
usage: '[resume <id|name>]',
|
|
255
255
|
argsHint: '[resume <id|name>]',
|
|
256
256
|
async handler(_args, ctx) {
|
|
257
|
-
// Matches the TUI's /sessions (
|
|
257
|
+
// Matches the TUI's /sessions (worst-class collision #5): this
|
|
258
258
|
// was `hidden: true` here — invisible in help/autocomplete — even
|
|
259
259
|
// though it is a fully working command identical in behavior to the
|
|
260
260
|
// TUI's visible one. Also forward args to /session (the TUI's fix for
|
|
@@ -479,7 +479,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
|
|
|
479
479
|
return false;
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
// NOTE (
|
|
482
|
+
// NOTE (core-verb pass): this file used to also export
|
|
483
483
|
// registerSessionWorkflowCommands(), a second top-level `/session` (alias
|
|
484
484
|
// `sess`) registration with its own usage text. It was NEVER called from
|
|
485
485
|
// startup — commands.ts only registers `sessionCommand` from ./session.ts —
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* feed-context-factory.ts — Construction and mutable-field sync for InputFeedContext.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from handler.ts (
|
|
4
|
+
* Extracted from handler.ts (0.18.23) to keep handler.ts under the
|
|
5
5
|
* 800-line architecture cap.
|
|
6
6
|
*
|
|
7
7
|
* Two exported functions:
|
|
@@ -95,9 +95,9 @@ export interface FeedContextStableRefs {
|
|
|
95
95
|
selection: SelectionManager;
|
|
96
96
|
pasteRegistry: Map<string, string>;
|
|
97
97
|
imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
98
|
-
/**
|
|
98
|
+
/** Ported from goodvibes-tui's DEBT-5 item 5; mutated in place, never reallocated. */
|
|
99
99
|
burstGuard: PanelBurstGuardState;
|
|
100
|
-
/**
|
|
100
|
+
/** OS-level terminal focus tracker, ported from goodvibes-tui's W2.3. */
|
|
101
101
|
focusTracker: FocusTracker;
|
|
102
102
|
projectRoot: string;
|
|
103
103
|
selectionModal: SelectionModal;
|
|
@@ -182,7 +182,7 @@ export function buildInitialFeedContext(
|
|
|
182
182
|
...mutable,
|
|
183
183
|
// --- requestRender: placeholder, swapped per-feed to buffered version ---
|
|
184
184
|
requestRender: noop,
|
|
185
|
-
//
|
|
185
|
+
// Wiring-layer-only bookkeeping for the paste-flood guard's honest
|
|
186
186
|
// resolution notice (not part of the ported panel-paste-flood-guard.ts
|
|
187
187
|
// module itself; see handler-feed.ts's feedInputTokens).
|
|
188
188
|
burstSuppressedCount: 0,
|
|
@@ -64,13 +64,13 @@ import type { FocusTracker } from '../core/focus-tracker.ts';
|
|
|
64
64
|
* - `inputHistory`, `conversationManager` — late-wired service handles; synced at
|
|
65
65
|
* feed() entry only since no in-feed action rewires them
|
|
66
66
|
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
67
|
-
* - `burstGuard` (
|
|
67
|
+
* - `burstGuard` (ported from goodvibes-tui's DEBT-5 item 5) — the
|
|
68
68
|
* unbracketed-paste-flood guard's sliding-window state, mutated in place
|
|
69
69
|
* across tokens by trackPanelPasteFloodGuard (see panel-paste-flood-guard.ts).
|
|
70
70
|
* Never reallocated. `burstSuppressedCount` is this wiring layer's own
|
|
71
71
|
* bookkeeping (not part of the ported module) for the honest resolution
|
|
72
72
|
* notice — see feedInputTokens below.
|
|
73
|
-
* - `focusTracker` (
|
|
73
|
+
* - `focusTracker` (ported from goodvibes-tui's W2.3) — tracks OS-level
|
|
74
74
|
* terminal focus from `\x1b[I`/`\x1b[O` tokens (DECSET ?1004h, enabled in
|
|
75
75
|
* main.ts). Shared instance from RuntimeServices, threaded via
|
|
76
76
|
* uiServices.platform.focusTracker (mirrors the TUI's own wiring).
|
|
@@ -105,11 +105,11 @@ export interface InputFeedContext {
|
|
|
105
105
|
contentWidth: number;
|
|
106
106
|
readonly pasteRegistry: Map<string, string>;
|
|
107
107
|
readonly imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
108
|
-
/**
|
|
108
|
+
/** Ported from goodvibes-tui DEBT-5 item 5 — mutated in place, never reallocated. */
|
|
109
109
|
readonly burstGuard: PanelBurstGuardState;
|
|
110
|
-
/**
|
|
110
|
+
/** Wiring-layer bookkeeping (not part of the ported module) for the honest suppressed-count notice. */
|
|
111
111
|
burstSuppressedCount: number;
|
|
112
|
-
/**
|
|
112
|
+
/** Ported from goodvibes-tui W2.3 — OS-level terminal focus, fed from 'focus' tokens below. */
|
|
113
113
|
readonly focusTracker: FocusTracker;
|
|
114
114
|
readonly projectRoot: string;
|
|
115
115
|
readonly selection: SelectionManager;
|
|
@@ -176,13 +176,13 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
176
176
|
const scrollTop = context.getScrollTop();
|
|
177
177
|
const lineCount = history.getLineCount();
|
|
178
178
|
const keybindings = context.keybindingsManager;
|
|
179
|
-
//
|
|
179
|
+
// One `now` per feed() call (not per token) — a genuine unbracketed-paste
|
|
180
180
|
// flood delivers many tokens in one drain, and they should all measure as
|
|
181
181
|
// arriving "at once" (mirrors goodvibes-tui's handler-feed.ts DEBT-5 item 5 doc).
|
|
182
182
|
const now = Date.now();
|
|
183
183
|
|
|
184
184
|
for (const token of tokens) {
|
|
185
|
-
//
|
|
185
|
+
// Ported from goodvibes-tui W2.3: focus-reporting tokens (CSI ?1004h)
|
|
186
186
|
// never reach the composer or any modal route — consumed here, first,
|
|
187
187
|
// unconditionally. No render needed.
|
|
188
188
|
if (token.type === 'focus') {
|
|
@@ -294,8 +294,8 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
//
|
|
298
|
-
// 5
|
|
297
|
+
// Ported from goodvibes-tui's panel-paste-flood-guard.ts, DEBT-5 item
|
|
298
|
+
// 5: guards command-mode's key-driven dispatch (handleCommandModeToken,
|
|
299
299
|
// below) from an unbracketed-paste-replay or control-character-injection
|
|
300
300
|
// burst.
|
|
301
301
|
//
|
package/src/input/handler.ts
CHANGED
|
@@ -94,7 +94,7 @@ export class InputHandler {
|
|
|
94
94
|
public pasteRegistry = new Map<string, string>();
|
|
95
95
|
public nextPasteId = 1;
|
|
96
96
|
public lastCtrlCTime = 0;
|
|
97
|
-
/**
|
|
97
|
+
/** Ported from goodvibes-tui DEBT-5 item 5 — unbracketed-paste-flood guard state, mutated in place. */
|
|
98
98
|
public burstGuard: PanelBurstGuardState = { timestamps: [], suspended: false, hintShown: false };
|
|
99
99
|
/** Long-lived feed context — reused across every feed() call to avoid per-keystroke allocation. */
|
|
100
100
|
public feedContext!: import('./handler-feed.ts').InputFeedContext;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// panel-paste-flood-guard.ts — ported from goodvibes-tui (commit 90eb3a26,
|
|
3
|
-
// DEBT-5 item 5)
|
|
3
|
+
// DEBT-5 item 5).
|
|
4
4
|
//
|
|
5
5
|
// A terminal WITHOUT bracketed paste delivers a pasted block as a burst of
|
|
6
6
|
// discrete 1-char 'text' tokens (isPasteToken stays false for every one of
|
|
7
7
|
// them, since that flag only fires for a single token whose value.length > 1).
|
|
8
8
|
//
|
|
9
|
-
// AGENT ADAPTATION (this repo has no `src/panels/` — see the
|
|
10
|
-
// matrix's
|
|
9
|
+
// AGENT ADAPTATION (this repo has no `src/panels/` — see the parity
|
|
10
|
+
// matrix's corresponding row): the TUI wires this guard inside handlePanelFocusToken,
|
|
11
11
|
// where it protects a focused PANEL from having each replayed character
|
|
12
12
|
// dispatched as a real hotkey (K arms kill, etc). This agent has no panel
|
|
13
13
|
// focus concept at all — every token that isn't consumed by a modal route or
|
|
@@ -640,7 +640,7 @@ export class SettingsModal {
|
|
|
640
640
|
if (this.groups.has(cat)) this.groups.get(cat)!.push(entry);
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
-
//
|
|
643
|
+
// Inject the synthetic display.themeMode enum (auto|dark|light) —
|
|
644
644
|
// agent-local key stored under the existing `display` section (not in the
|
|
645
645
|
// SDK ConfigKey union; setDynamic/get round-trip it), the TUI's
|
|
646
646
|
// settings-modal-data.ts synthetic-setting pattern. Cycles like any other
|
package/src/main.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { FOCUS_ENABLE, FOCUS_DISABLE, installFocusModeExitGuard, markFocusModeEn
|
|
|
53
53
|
import { prepareShellCliRuntime } from './cli/entrypoint.ts';
|
|
54
54
|
import { applyInitialTuiCliState, formatFatalStartupErrorForLog, formatFatalStartupErrorForUser, getInteractiveTerminalLaunchError } from './cli/tui-startup.ts';
|
|
55
55
|
import { wireSpokenTurnRuntime } from './audio/spoken-turn-wiring.ts';
|
|
56
|
+
import { createUnhandledRejectionHandler } from './runtime/unhandled-rejection-guard.ts';
|
|
56
57
|
import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './audio/spoken-turn-model-routing.ts';
|
|
57
58
|
import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
|
|
58
59
|
import { buildCommandArgsHint } from './input/command-args-hint.ts';
|
|
@@ -67,7 +68,7 @@ const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u', KEYBOARD_EXT_DISABLE = '\
|
|
|
67
68
|
async function main() {
|
|
68
69
|
const stdout = process.stdout;
|
|
69
70
|
const stdin = process.stdin;
|
|
70
|
-
installFocusModeExitGuard(stdout); //
|
|
71
|
+
installFocusModeExitGuard(stdout); // see shell/terminal-focus-mode.ts
|
|
71
72
|
const { cli, configManager, bootstrapWorkingDir, bootstrapHomeDirectory } = await prepareShellCliRuntime(process.argv.slice(2), {
|
|
72
73
|
defaultWorkingDirectory: process.env['GOODVIBES_WORKING_DIR'] ?? process.cwd(),
|
|
73
74
|
homeDirectory: process.env['GOODVIBES_AGENT_HOME'] ?? homedir(),
|
|
@@ -159,7 +160,7 @@ async function main() {
|
|
|
159
160
|
|
|
160
161
|
let streamTokenSpeed = 0;
|
|
161
162
|
|
|
162
|
-
const thinkingClock = new ThinkingStallClock(); //
|
|
163
|
+
const thinkingClock = new ThinkingStallClock(); // thinking-indicator stall clock
|
|
163
164
|
|
|
164
165
|
let scrollTop = 0;
|
|
165
166
|
let scrollLocked = true;
|
|
@@ -221,43 +222,34 @@ async function main() {
|
|
|
221
222
|
|
|
222
223
|
const unsubs: Array<() => void> = [];
|
|
223
224
|
let recoveryInterval: ReturnType<typeof setInterval> | null = null;
|
|
224
|
-
let stopSpokenOutputForExit: (() => void) | null = null;
|
|
225
|
+
let stopSpokenOutputForExit: (() => Promise<void>) | null = null;
|
|
225
226
|
let recoveryPending = false;
|
|
226
227
|
|
|
227
228
|
const sigintHandler = (): void => input.feed('\x03');
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (now - _unhandledRejectionWindowStart > 10000) {
|
|
233
|
-
_unhandledRejectionCount = 0;
|
|
234
|
-
_unhandledRejectionWindowStart = now;
|
|
235
|
-
}
|
|
236
|
-
_unhandledRejectionCount++;
|
|
237
|
-
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
238
|
-
if (_unhandledRejectionCount > 3) {
|
|
239
|
-
logger.error('CRITICAL: cascading unhandled rejections — consider restarting', {
|
|
240
|
-
count: _unhandledRejectionCount,
|
|
241
|
-
windowMs: now - _unhandledRejectionWindowStart,
|
|
242
|
-
error: String(reason),
|
|
243
|
-
});
|
|
244
|
-
systemMessageRouter.high(
|
|
245
|
-
`[Critical] Multiple errors detected (${_unhandledRejectionCount} in 10s). If the issue persists, please restart. Latest: ${msg}`
|
|
246
|
-
);
|
|
247
|
-
} else {
|
|
248
|
-
systemMessageRouter.high(`[Error] ${msg}`);
|
|
249
|
-
logger.error('unhandledRejection', { error: String(reason) });
|
|
250
|
-
}
|
|
251
|
-
render();
|
|
252
|
-
};
|
|
229
|
+
const unhandledRejectionHandler = createUnhandledRejectionHandler({
|
|
230
|
+
notifyHigh: (message) => systemMessageRouter.high(message),
|
|
231
|
+
render: () => render(),
|
|
232
|
+
});
|
|
253
233
|
const resizeHandler = (): void => {
|
|
254
234
|
input.setContentWidth(getPromptContentWidth());
|
|
255
235
|
compositor.resetDiff();
|
|
256
236
|
render();
|
|
257
237
|
};
|
|
258
238
|
|
|
239
|
+
let exiting = false;
|
|
259
240
|
const exitApp = (): void => {
|
|
260
|
-
|
|
241
|
+
// Reentrancy guard: a second /exit or keypress during the bounded
|
|
242
|
+
// spoken-audio drain below must not re-run teardown.
|
|
243
|
+
if (exiting) return;
|
|
244
|
+
exiting = true;
|
|
245
|
+
// Exit lets the spoken audio the user is already hearing finish inside a
|
|
246
|
+
// short bounded window (capped inside stopForExit) instead of killing the
|
|
247
|
+
// player mid-drain; queued-but-unplayed speech is dropped. Deliberate
|
|
248
|
+
// interrupts (Ctrl+C, /tts stop) still cut instantly via spokenTurns.stop().
|
|
249
|
+
let spokenOutputDrain: Promise<void> = Promise.resolve();
|
|
250
|
+
try {
|
|
251
|
+
spokenOutputDrain = Promise.resolve(stopSpokenOutputForExit?.()).then(() => undefined);
|
|
252
|
+
} catch { /* non-fatal to exit */ }
|
|
261
253
|
unsubs.forEach(fn => fn());
|
|
262
254
|
// Persist last-seen before shutdown so the next launch can compute the digest.
|
|
263
255
|
autonomy.stop();
|
|
@@ -275,7 +267,9 @@ async function main() {
|
|
|
275
267
|
allowTerminalWrite(() => stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + FOCUS_DISABLE + CURSOR_SHOW + exitScreen));
|
|
276
268
|
terminalOutputGuard.dispose();
|
|
277
269
|
stdin.setRawMode(false);
|
|
278
|
-
process
|
|
270
|
+
// The terminal is already restored above; only the process exit waits for
|
|
271
|
+
// the (internally capped, ~2s max) audio drain.
|
|
272
|
+
void spokenOutputDrain.catch(() => undefined).then(() => process.exit(0));
|
|
279
273
|
};
|
|
280
274
|
|
|
281
275
|
commandContext.exit = exitApp;
|
|
@@ -286,7 +280,8 @@ async function main() {
|
|
|
286
280
|
events: uiServices.events,
|
|
287
281
|
notify: (message) => { systemMessageRouter.high(message); render(); },
|
|
288
282
|
});
|
|
289
|
-
|
|
283
|
+
// Exit-path stop: bounded drain of the audio already playing (see stopForExit).
|
|
284
|
+
stopSpokenOutputForExit = () => spokenTurns.stopForExit();
|
|
290
285
|
unsubs.push(...spokenTurns.unsubs);
|
|
291
286
|
unsubs.push(attachSpokenTurnModelRouting({
|
|
292
287
|
orchestrator,
|
|
@@ -401,7 +396,7 @@ async function main() {
|
|
|
401
396
|
sidebarOverride = !(sidebarWidthFor(width) > 0);
|
|
402
397
|
render();
|
|
403
398
|
};
|
|
404
|
-
const rawRequestPermission: typeof permissionPromptRef.requestPermission = (request) => new Promise((resolve) => { //
|
|
399
|
+
const rawRequestPermission: typeof permissionPromptRef.requestPermission = (request) => new Promise((resolve) => { // see shell/terminal-focus-mode.ts
|
|
405
400
|
pendingPermission = { ...request, resolve: (approved: boolean, remember = false) => resolve({ approved, remember }) };
|
|
406
401
|
render();
|
|
407
402
|
});
|
|
@@ -692,7 +687,7 @@ async function main() {
|
|
|
692
687
|
stdin.setEncoding('utf8');
|
|
693
688
|
allowTerminalWrite(() => { markFocusModeEnabled(); return stdout.write((cli.flags.noAltScreen ? '' : ALT_SCREEN_ENTER) + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE + FOCUS_ENABLE); });
|
|
694
689
|
|
|
695
|
-
//
|
|
690
|
+
// Forced dark/light before first paint; auto (TTY) probes + repaints once if light.
|
|
696
691
|
const themeProbe = installStartupThemeProbe({
|
|
697
692
|
configManager, stdout, writeAllowed: allowTerminalWrite,
|
|
698
693
|
resetDiff: () => compositor.resetDiff(), render,
|
|
@@ -28,7 +28,7 @@ function formatMegabytes(bytes: number): string {
|
|
|
28
28
|
return `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// The memory/routine counts below are re-derived live on every
|
|
32
32
|
// repaint (AgentWorkspace.syncLiveCountersForRender). If that live read
|
|
33
33
|
// fails, liveCountersStale flags it so we say so instead of asserting a
|
|
34
34
|
// number the disk might already contradict.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* startup-theme-probe.ts
|
|
2
|
+
* startup-theme-probe.ts — install the OSC-11 background probe at
|
|
3
3
|
* startup and wire its resolved mode to the ported theme system.
|
|
4
4
|
*
|
|
5
5
|
* Thin composition seam extracted from main.ts: it binds R2's
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// status-glyphs.ts — canonical glyph map for status states.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// contract (@pellux/goodvibes-sdk/platform/presentation
|
|
4
|
+
// STATE_GLYPHS is no longer hardcoded here. It is the SDK presentation
|
|
5
|
+
// contract (@pellux/goodvibes-sdk/platform/presentation),
|
|
6
6
|
// aliased to GLYPHS.status so the four semantic glyphs are spelled out in
|
|
7
7
|
// exactly one place and can never drift from the registry again. Re-exported
|
|
8
8
|
// under the historical names so status-token.ts and polish.ts import unchanged.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* is enabled; otherwise the query is swallowed and the timeout fallback (dark)
|
|
27
27
|
* covers it silently.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
29
|
+
* Adaptation for this agent: the theme-application coupling is INJECTED rather
|
|
30
30
|
* than imported from theme.ts. R2 owns this module; R4 owns the theme system
|
|
31
31
|
* (theme.ts / theme-mode-config.ts) and wires installBackgroundThemeProbe with
|
|
32
32
|
* `applyThemeMode: setActiveThemeMode`. Keeping the coupling injected lets R2
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* and the entry file stays within the source-file line-count gate.
|
|
6
6
|
*
|
|
7
7
|
* FOCUS_ENABLE / FOCUS_DISABLE (DECSET ?1004) are the shared home for the OS
|
|
8
|
-
* window-focus reporting mode consumed by the focus-tracker
|
|
9
|
-
* FOCUS_ENABLE from here rather
|
|
8
|
+
* window-focus reporting mode consumed by the focus-tracker: the focus-mode
|
|
9
|
+
* module (shell/terminal-focus-mode.ts) imports FOCUS_ENABLE from here rather
|
|
10
|
+
* than redefining it.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
export const ALT_SCREEN_ENTER = '\x1b[?1049h';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* theme-mode-config — the appearance/theme-mode preference surface
|
|
2
|
+
* theme-mode-config — the appearance/theme-mode preference surface.
|
|
3
3
|
*
|
|
4
4
|
* The preference lives at the config key `display.themeMode` (auto | dark |
|
|
5
5
|
* light, default auto). It is stored under the existing SDK `display` section
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* display.* toggles.
|
|
9
9
|
*
|
|
10
10
|
* DIVERGENCE FROM THE W4 BRIEF (recorded): the brief listed theme-mode-config.ts
|
|
11
|
-
* as the NEW home for the config-read helpers, but
|
|
11
|
+
* as the NEW home for the config-read helpers, but the terminal-bg-probe.ts change landed FIRST and
|
|
12
12
|
* inlined resolveConfiguredThemeMode / coerceThemeModeSetting /
|
|
13
13
|
* THEME_MODE_CONFIG_KEY / ThemeModeSetting into terminal-bg-probe.ts (so R2
|
|
14
14
|
* could ship the probe before this file existed). Rather than duplicate those
|
package/src/renderer/theme.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* theme.ts — Semantic colour token layer (
|
|
2
|
+
* theme.ts — Semantic colour token layer (port of the TUI theme system).
|
|
3
3
|
*
|
|
4
4
|
* Two token layers, resolved per background mode:
|
|
5
5
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Dark mode values are the historically used colours. Light mode values exist
|
|
18
18
|
* for correctness parity and are consumed once the terminal-bg-probe
|
|
19
|
-
* (terminal-escapes / OSC 11
|
|
19
|
+
* (terminal-escapes / OSC 11) resolves the mode and setActiveThemeMode
|
|
20
20
|
* is called. Callers that do not yet have mode detection get 'dark' — the safe
|
|
21
21
|
* default (activeMode starts dark).
|
|
22
22
|
*
|
|
@@ -162,7 +162,7 @@ export const DARK_THEME: Readonly<ThemeTokens> = DARK;
|
|
|
162
162
|
//
|
|
163
163
|
// The dark table is the SDK's TONE_TOKENS (re-exported here as UI_TONES); the
|
|
164
164
|
// light variant is the SDK's resolveTones('light'). The agent does NOT mint its
|
|
165
|
-
// own light chrome variant — that duplication is exactly what
|
|
165
|
+
// own light chrome variant — that duplication is exactly what the SDK extraction ended.
|
|
166
166
|
// ---------------------------------------------------------------------------
|
|
167
167
|
|
|
168
168
|
/** The chrome tone-token shape (the SDK ToneTokens contract). */
|
|
@@ -189,7 +189,7 @@ export function resolveUiTones(mode: ThemeMode): Readonly<UiToneTokens> {
|
|
|
189
189
|
//
|
|
190
190
|
// registerThemeRefresh exists for owners that BAKE tone values into
|
|
191
191
|
// module-level constants (which cannot be re-resolved per call). Nothing
|
|
192
|
-
// registers
|
|
192
|
+
// registers currently — the agent's opaque panel palettes (polish.ts
|
|
193
193
|
// DEFAULT_PANEL_PALETTE and the modal/overlay/fullscreen surfaces built from
|
|
194
194
|
// it) paint OPAQUE dark boxes whose fg/state tokens stay dark in both modes, so
|
|
195
195
|
// they need no rebuild for dark parity (the opaque-surface trio deferral). The
|