@pellux/goodvibes-agent 1.5.5 → 1.5.6
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 +9 -0
- package/README.md +1 -1
- package/dist/package/main.js +94130 -79335
- package/docs/tools-and-commands.md +2 -1
- package/package.json +6 -3
- package/src/agent/autonomy-schedule-format.ts +1 -1
- package/src/agent/autonomy-schedule.ts +3 -3
- package/src/agent/calendar/calendar-oauth-service.ts +324 -0
- package/src/agent/calendar-subscription-registry.ts +497 -0
- package/src/agent/email/email-service.ts +57 -0
- package/src/agent/email/smtp-client.ts +30 -10
- package/src/agent/memory-prompt.ts +143 -12
- package/src/agent/operator-actions.ts +26 -22
- package/src/agent/prompt-context-receipts.ts +45 -18
- package/src/agent/reminder-schedule-format.ts +1 -1
- package/src/agent/reminder-schedule.ts +3 -3
- package/src/agent/routine-schedule-format.ts +2 -2
- package/src/agent/routine-schedule-promotion.ts +5 -5
- package/src/agent/routine-schedule-receipts.ts +2 -2
- package/src/agent/schedule-edit-format.ts +1 -1
- package/src/agent/schedule-edit.ts +5 -5
- package/src/agent/session-registration.ts +281 -0
- package/src/agent/vibe-file.ts +50 -0
- package/src/cli/local-library-command-shared.ts +146 -0
- package/src/cli/management.ts +2 -1
- package/src/cli/personas-command.ts +252 -0
- package/src/cli/resume-relaunch-notice.ts +151 -0
- package/src/cli/routines-command.ts +25 -2
- package/src/cli/service-posture.ts +3 -2
- package/src/cli/skill-bundle-command.ts +175 -0
- package/src/cli/skills-command.ts +309 -0
- package/src/cli/status.ts +43 -7
- package/src/cli/tui-startup.ts +24 -1
- package/src/config/agent-settings-policy.ts +0 -1
- package/src/config/secret-config.ts +4 -0
- package/src/core/conversation-rendering.ts +20 -11
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/system-message-noise.ts +108 -0
- package/src/core/system-message-router.ts +54 -1
- package/src/core/thinking-overlay.ts +83 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +75 -108
- package/src/input/agent-workspace-access-command-editors.ts +108 -128
- package/src/input/agent-workspace-activation.ts +15 -0
- package/src/input/agent-workspace-basic-command-editor-submission.ts +292 -513
- package/src/input/agent-workspace-basic-command-editors.ts +433 -564
- package/src/input/agent-workspace-calendar-connect-editor.ts +116 -0
- package/src/input/agent-workspace-calendar-oauth-editor.ts +152 -0
- package/src/input/agent-workspace-calendar-subscribe-editor.ts +128 -0
- package/src/input/agent-workspace-categories.ts +7 -2
- package/src/input/agent-workspace-channel-command-editor-submission.ts +38 -52
- package/src/input/agent-workspace-channel-command-editors.ts +42 -46
- package/src/input/agent-workspace-command-editor-engine.ts +133 -0
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-direct-editor-submission.ts +59 -0
- package/src/input/agent-workspace-email-connect-editor.ts +162 -0
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +52 -81
- package/src/input/agent-workspace-knowledge-command-editors.ts +71 -71
- package/src/input/agent-workspace-library-command-editor-submission.ts +10 -28
- package/src/input/agent-workspace-library-command-editors.ts +16 -2
- package/src/input/agent-workspace-live-counters.ts +55 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +60 -82
- package/src/input/agent-workspace-mcp-command-editors.ts +52 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +12 -39
- package/src/input/agent-workspace-media-command-editors.ts +10 -8
- package/src/input/agent-workspace-memory-command-editor-submission.ts +76 -151
- package/src/input/agent-workspace-memory-command-editors.ts +116 -141
- package/src/input/agent-workspace-operations-command-editor-submission.ts +131 -184
- package/src/input/agent-workspace-operations-command-editors.ts +150 -162
- package/src/input/agent-workspace-provider-command-editor-submission.ts +60 -106
- package/src/input/agent-workspace-provider-command-editors.ts +58 -68
- package/src/input/agent-workspace-search.ts +8 -1
- package/src/input/agent-workspace-session-command-editor-submission.ts +104 -132
- package/src/input/agent-workspace-session-command-editors.ts +160 -195
- package/src/input/agent-workspace-settings.ts +40 -1
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +55 -71
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +65 -69
- package/src/input/agent-workspace-snapshot-builders.ts +431 -0
- package/src/input/agent-workspace-snapshot-config.ts +43 -0
- package/src/input/agent-workspace-snapshot.ts +198 -432
- package/src/input/agent-workspace-task-command-editor-submission.ts +34 -44
- package/src/input/agent-workspace-task-command-editors.ts +27 -28
- package/src/input/agent-workspace-types.ts +35 -1
- package/src/input/agent-workspace.ts +5 -14
- package/src/input/command-registry.ts +23 -1
- package/src/input/commands/calendar-connect-runtime.ts +226 -0
- package/src/input/commands/calendar-runtime.ts +110 -7
- package/src/input/commands/calendar-subscription-runtime.ts +225 -0
- package/src/input/commands/email-runtime.ts +100 -40
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/network-scan-runtime.ts +75 -0
- package/src/input/commands/operator-actions-runtime.ts +9 -6
- package/src/input/commands/personas-runtime.ts +1 -1
- package/src/input/commands/schedule-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +13 -1
- package/src/input/commands/session-workflow.ts +15 -20
- package/src/input/commands/session.ts +3 -1
- package/src/input/commands/shell-core.ts +20 -6
- package/src/input/commands.ts +2 -2
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +10 -0
- package/src/input/handler-feed.ts +87 -0
- package/src/input/handler-modal-routes.ts +6 -1
- package/src/input/handler.ts +5 -0
- package/src/input/panel-paste-flood-guard.ts +94 -0
- package/src/input/settings-modal-types.ts +5 -3
- package/src/input/settings-modal.ts +21 -0
- package/src/main.ts +36 -36
- package/src/permissions/approval-posture.ts +141 -0
- package/src/renderer/agent-workspace-context-lines.ts +10 -2
- package/src/renderer/compositor.ts +27 -4
- package/src/renderer/diff.ts +61 -18
- package/src/renderer/fullscreen-primitives.ts +37 -18
- package/src/renderer/markdown.ts +20 -10
- package/src/renderer/modal-factory.ts +25 -15
- package/src/renderer/overlay-box.ts +23 -12
- package/src/renderer/process-indicator.ts +8 -3
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -0
- package/src/renderer/startup-theme-probe.ts +35 -0
- package/src/renderer/status-glyphs.ts +11 -15
- package/src/renderer/system-message.ts +17 -2
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/terminal-bg-probe.ts +373 -0
- package/src/renderer/terminal-escapes.ts +24 -0
- package/src/renderer/theme-mode-config.ts +87 -0
- package/src/renderer/theme.ts +241 -0
- package/src/renderer/thinking.ts +12 -3
- package/src/renderer/tool-call.ts +7 -3
- package/src/renderer/ui-factory.ts +92 -36
- package/src/renderer/ui-primitives.ts +33 -93
- package/src/runtime/bootstrap-core.ts +15 -0
- package/src/runtime/bootstrap-hook-bridge.ts +6 -0
- package/src/runtime/bootstrap-shell.ts +2 -0
- package/src/runtime/bootstrap.ts +68 -5
- package/src/runtime/calendar-boot-refresh.ts +105 -0
- package/src/runtime/lan-scan-consent.ts +253 -0
- package/src/runtime/services.ts +127 -2
- package/src/runtime/session-spine-rest-transport.ts +160 -0
- package/src/runtime/terminal-output-guard.ts +6 -1
- package/src/runtime/ui-services.ts +3 -0
- package/src/shell/agent-workspace-fullscreen.ts +5 -0
- package/src/shell/terminal-focus-mode.ts +120 -0
- package/src/shell/ui-openers.ts +13 -4
- package/src/tools/agent-harness-autonomy-intake.ts +5 -5
- package/src/tools/agent-harness-autonomy-queue.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +2 -1
- package/src/tools/agent-harness-metadata.ts +16 -8
- package/src/tools/agent-harness-operator-methods.ts +44 -8
- package/src/tools/agent-harness-personal-ops-discovery.ts +57 -10
- package/src/tools/agent-harness-personal-ops-lanes.ts +17 -5
- package/src/tools/agent-harness-personal-ops-operations.ts +7 -7
- package/src/tools/agent-harness-personal-ops-types.ts +6 -0
- package/src/tools/agent-harness-prompt-context.ts +26 -12
- package/src/tools/agent-harness-workspace-actions.ts +4 -0
- package/src/tools/agent-local-registry-args.ts +117 -0
- package/src/tools/agent-local-registry-memory.ts +227 -0
- package/src/tools/agent-local-registry-tool.ts +19 -237
- package/src/tools/agent-operator-briefing-tool.ts +2 -2
- package/src/tools/agent-operator-method-tool.ts +13 -0
- package/src/tools/agent-policy-explanation.ts +39 -4
- package/src/tools/agent-schedule-tool.ts +5 -5
- package/src/utils/terminal-width.ts +98 -1
- package/src/version.ts +1 -1
- package/src/cli/local-library-command.ts +0 -825
|
@@ -1,96 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
selected: '▸',
|
|
23
|
-
collapsed: '▸',
|
|
24
|
-
expanded: '▾',
|
|
25
|
-
up: '↑',
|
|
26
|
-
down: '↓',
|
|
27
|
-
moreAbove: '▲',
|
|
28
|
-
moreBelow: '▼',
|
|
29
|
-
next: '→',
|
|
30
|
-
back: '←',
|
|
31
|
-
pipeSeparator: '│',
|
|
32
|
-
},
|
|
33
|
-
status: {
|
|
34
|
-
success: '✓',
|
|
35
|
-
failure: '✕',
|
|
36
|
-
pending: '•',
|
|
37
|
-
active: '●',
|
|
38
|
-
idle: '○',
|
|
39
|
-
info: '•',
|
|
40
|
-
blocked: '⊘',
|
|
41
|
-
skipped: '◇',
|
|
42
|
-
review: '◈',
|
|
43
|
-
retry: '↻',
|
|
44
|
-
handoff: '⇢',
|
|
45
|
-
reference: '↗',
|
|
46
|
-
partial: '◐',
|
|
47
|
-
dualPane: '◆',
|
|
48
|
-
star: '★',
|
|
49
|
-
},
|
|
50
|
-
meter: {
|
|
51
|
-
filled: '█',
|
|
52
|
-
medium: '▓',
|
|
53
|
-
light: '▒',
|
|
54
|
-
empty: '░',
|
|
55
|
-
spark: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
|
|
56
|
-
},
|
|
57
|
-
} as const;
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ui-primitives.ts — glyph registry + tone-token table.
|
|
3
|
+
//
|
|
4
|
+
// W4-R4: these four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
|
|
5
|
+
// no longer minted locally. They are the SDK presentation contract
|
|
6
|
+
// (@pellux/goodvibes-sdk/platform/presentation, landed by W4-S1), consumed here
|
|
7
|
+
// so the agent and the TUI share ONE source (Mike's move-to-SDK ruling —
|
|
8
|
+
// machinery needed by 2+ surfaces => SDK). See
|
|
9
|
+
// docs/decisions/2026-07-05-presentation-contract-sdk-extraction.md in the SDK.
|
|
10
|
+
//
|
|
11
|
+
// Re-exported under the historical names (GLYPHS, UI_TONES) so every existing
|
|
12
|
+
// importer keeps working with no call-site churn. UI_TONES is the dark-mode
|
|
13
|
+
// tone table (== resolveTones('dark')); light is resolved via theme.ts's
|
|
14
|
+
// activeUiTones() / resolveUiTones(), which composes the mode dimension over
|
|
15
|
+
// the SDK's resolveTones().
|
|
16
|
+
//
|
|
17
|
+
// Visible glyph convergence (deliberate, per S1's divergence ruling): the
|
|
18
|
+
// agent's status glyphs adopt the TUI reference — idle ○ (U+25CB) -> ◌ (U+25CC),
|
|
19
|
+
// info • (U+2022) -> ○ (U+25CB), and a new warn ⚠ key. Called out here so the
|
|
20
|
+
// render-time change is not mistaken for a regression.
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
58
22
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
inverse: '#0f172a',
|
|
66
|
-
},
|
|
67
|
-
bg: {
|
|
68
|
-
base: '#11131a',
|
|
69
|
-
surface: '#161a22',
|
|
70
|
-
title: '#0f172a',
|
|
71
|
-
section: '#18202b',
|
|
72
|
-
summary: '#1b2430',
|
|
73
|
-
selected: '#223049',
|
|
74
|
-
input: '#1e293b',
|
|
75
|
-
warning: '#2b2116',
|
|
76
|
-
error: '#2a161b',
|
|
77
|
-
success: '#14241b',
|
|
78
|
-
},
|
|
79
|
-
state: {
|
|
80
|
-
info: '#38bdf8',
|
|
81
|
-
good: '#22c55e',
|
|
82
|
-
warn: '#f59e0b',
|
|
83
|
-
bad: '#ef4444',
|
|
84
|
-
blocked: '#f97316',
|
|
85
|
-
active: '#60a5fa',
|
|
86
|
-
},
|
|
87
|
-
accent: {
|
|
88
|
-
browser: '#7dd3fc',
|
|
89
|
-
control: '#22d3ee',
|
|
90
|
-
inspector: '#c4b5fd',
|
|
91
|
-
workflow: '#fbbf24',
|
|
92
|
-
conversation: '#93c5fd',
|
|
93
|
-
},
|
|
94
|
-
} as const;
|
|
23
|
+
import {
|
|
24
|
+
GLYPHS,
|
|
25
|
+
TONE_TOKENS,
|
|
26
|
+
DIFF_TONES,
|
|
27
|
+
SPINNER_FRAMES,
|
|
28
|
+
} from '@pellux/goodvibes-sdk/platform/presentation';
|
|
95
29
|
|
|
30
|
+
export { GLYPHS, DIFF_TONES, SPINNER_FRAMES };
|
|
31
|
+
|
|
32
|
+
/** The dark-mode tone-token table (== resolveTones('dark')). */
|
|
33
|
+
export const UI_TONES = TONE_TOKENS;
|
|
34
|
+
|
|
35
|
+
/** The glyph registry shape, preserved for existing type references. */
|
|
96
36
|
export type UiGlyphRegistry = typeof GLYPHS;
|
|
@@ -518,6 +518,21 @@ export async function initializeBootstrapCore(
|
|
|
518
518
|
},
|
|
519
519
|
}).catch((err) => { logger.debug('session broker create session failed at bootstrap', { err }); });
|
|
520
520
|
|
|
521
|
+
// W2A: mirror the create into the daemon spine (fire-and-forget; the local
|
|
522
|
+
// broker above stays the source of truth). kind:'agent' is the canonical record
|
|
523
|
+
// identity; participant.surfaceKind stays 'service' (the TRANSPORT axis).
|
|
524
|
+
services.sessionSpineClient.register({
|
|
525
|
+
sessionId: runtime.sessionId,
|
|
526
|
+
project: workingDir,
|
|
527
|
+
title: 'GoodVibes Agent session',
|
|
528
|
+
});
|
|
529
|
+
// Debounced heartbeat off turn activity — coalesced to one wire call per window,
|
|
530
|
+
// no title, reopen:false. Uses the ref so a resumed session id is followed.
|
|
531
|
+
runtimeUnsubs.push(
|
|
532
|
+
uiServices.events.turns.on('TURN_SUBMITTED', () => services.sessionSpineClient.heartbeat(runtimeSessionIdRef.value)),
|
|
533
|
+
uiServices.events.turns.on('TURN_COMPLETED', () => services.sessionSpineClient.heartbeat(runtimeSessionIdRef.value)),
|
|
534
|
+
);
|
|
535
|
+
|
|
521
536
|
domainDispatch.syncSessionState({
|
|
522
537
|
id: userSessionId,
|
|
523
538
|
projectRoot: workingDir,
|
|
@@ -9,6 +9,7 @@ import { HelperModel } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
9
9
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
10
10
|
import { formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
|
|
11
11
|
import type { SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
12
|
+
import type { SessionSpineClient } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
12
13
|
import type { SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
13
14
|
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
14
15
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -21,6 +22,8 @@ export interface ResumeSessionOptions {
|
|
|
21
22
|
readonly requestRender: () => void;
|
|
22
23
|
readonly onSessionIdChanged?: (sessionId: string) => void;
|
|
23
24
|
readonly sharedSessionBroker: Pick<SharedSessionBroker, 'reopenSession'>;
|
|
25
|
+
readonly sessionSpineClient: Pick<SessionSpineClient, 'reopen'>;
|
|
26
|
+
readonly projectRoot: string;
|
|
24
27
|
readonly writeLastSessionPointer: (sessionId: string) => void;
|
|
25
28
|
readonly hookDispatcher: HookDispatcher;
|
|
26
29
|
readonly sessionManager: SessionManager;
|
|
@@ -51,6 +54,9 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
|
|
|
51
54
|
if (meta?.provider) options.runtime.provider = meta.provider;
|
|
52
55
|
options.writeLastSessionPointer(sessionId);
|
|
53
56
|
void options.sharedSessionBroker.reopenSession(sessionId).catch((err) => { logger.debug('session broker reopen session failed', { err }); });
|
|
57
|
+
// W2A: mirror the reopen into the daemon spine — reopen:true is sent ONLY on
|
|
58
|
+
// this explicit user resume verb (fire-and-forget; never blocks the resume).
|
|
59
|
+
options.sessionSpineClient.reopen({ sessionId, project: options.projectRoot });
|
|
54
60
|
options.conversation.log(`Resumed session: ${sessionId}`, { fg: '135' });
|
|
55
61
|
const returnContextMode = getReturnContextMode(options.configManager);
|
|
56
62
|
if (returnContextMode !== 'off' && meta.returnContext) {
|
|
@@ -131,6 +131,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
131
131
|
requestRender,
|
|
132
132
|
onSessionIdChanged,
|
|
133
133
|
sharedSessionBroker: services.sessionBroker,
|
|
134
|
+
sessionSpineClient: services.sessionSpineClient,
|
|
135
|
+
projectRoot: services.shellPaths.workingDirectory,
|
|
134
136
|
writeLastSessionPointer,
|
|
135
137
|
hookDispatcher: services.hookDispatcher,
|
|
136
138
|
sessionManager: services.sessionManager,
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -27,8 +27,10 @@ import {
|
|
|
27
27
|
loadLastConversation,
|
|
28
28
|
writeLastSessionPointer,
|
|
29
29
|
} from '@/runtime/index.ts';
|
|
30
|
-
import { scheduleBackgroundMcpDiscovery
|
|
30
|
+
import { scheduleBackgroundMcpDiscovery } from '@/runtime/index.ts';
|
|
31
31
|
import { restoreSavedModel } from '@/runtime/index.ts';
|
|
32
|
+
import { runGatedLanScan } from './lan-scan-consent.ts';
|
|
33
|
+
import { scheduleCalendarSubscriptionBootRefresh } from './calendar-boot-refresh.ts';
|
|
32
34
|
import type { ExternalServicesHandle, HostServiceStatus, TurnEvent } from '@/runtime/index.ts';
|
|
33
35
|
import type { UiRuntimeServices } from './ui-services.ts';
|
|
34
36
|
import { createDeferredStartupCoordinator } from '@/runtime/index.ts';
|
|
@@ -37,6 +39,7 @@ import { createBootstrapShell } from './bootstrap-shell.ts';
|
|
|
37
39
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
38
40
|
import { startMcpConfigAutoReload } from '../mcp/runtime-reload.ts';
|
|
39
41
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
42
|
+
import { foldLegacySpineStore } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
40
43
|
import { AgentPromptContextReceiptStore, composeRuntimePromptWithReceipt } from '../agent/prompt-context-receipts.ts';
|
|
41
44
|
import { registerAgentAuditTool } from '../tools/agent-audit-tool.ts';
|
|
42
45
|
import { registerAgentAutonomyTool } from '../tools/agent-autonomy-tool.ts';
|
|
@@ -200,9 +203,17 @@ export async function bootstrapRuntime(
|
|
|
200
203
|
services.shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'prompt-context-receipts.jsonl'),
|
|
201
204
|
);
|
|
202
205
|
let activePromptTurnId: string | null = null;
|
|
206
|
+
// The current turn's raw text (Wave-4 W4-A1B): the injection seam
|
|
207
|
+
// (composeRuntimePromptWithReceipt, invoked from getSystemPrompt below) needs the
|
|
208
|
+
// active turn's intent to rank the already-eligible memory set by relevance. This is
|
|
209
|
+
// the real seam W4-A1 left unthreaded — TURN_SUBMITTED already carries `prompt`, it
|
|
210
|
+
// was just never captured. Mirrors activePromptTurnId's lifecycle exactly: set on
|
|
211
|
+
// TURN_SUBMITTED, cleared on every terminal event for that turn.
|
|
212
|
+
let activePromptTurnText: string | null = null;
|
|
203
213
|
runtimeUnsubs.push(
|
|
204
214
|
runtimeBus.on<Extract<TurnEvent, { type: 'TURN_SUBMITTED' }>>('TURN_SUBMITTED', (event) => {
|
|
205
215
|
activePromptTurnId = event.payload.turnId;
|
|
216
|
+
activePromptTurnText = event.payload.prompt;
|
|
206
217
|
}),
|
|
207
218
|
runtimeBus.on<Extract<TurnEvent, { type: 'TURN_COMPLETED' }>>('TURN_COMPLETED', (event) => {
|
|
208
219
|
promptContextReceipts.recordTurnOutcome({
|
|
@@ -211,7 +222,10 @@ export async function bootstrapRuntime(
|
|
|
211
222
|
terminalEvent: 'TURN_COMPLETED',
|
|
212
223
|
stopReason: event.payload.stopReason,
|
|
213
224
|
});
|
|
214
|
-
if (activePromptTurnId === event.payload.turnId)
|
|
225
|
+
if (activePromptTurnId === event.payload.turnId) {
|
|
226
|
+
activePromptTurnId = null;
|
|
227
|
+
activePromptTurnText = null;
|
|
228
|
+
}
|
|
215
229
|
}),
|
|
216
230
|
runtimeBus.on<Extract<TurnEvent, { type: 'TURN_ERROR' }>>('TURN_ERROR', (event) => {
|
|
217
231
|
promptContextReceipts.recordTurnOutcome({
|
|
@@ -221,7 +235,10 @@ export async function bootstrapRuntime(
|
|
|
221
235
|
stopReason: event.payload.stopReason,
|
|
222
236
|
detail: event.payload.error,
|
|
223
237
|
});
|
|
224
|
-
if (activePromptTurnId === event.payload.turnId)
|
|
238
|
+
if (activePromptTurnId === event.payload.turnId) {
|
|
239
|
+
activePromptTurnId = null;
|
|
240
|
+
activePromptTurnText = null;
|
|
241
|
+
}
|
|
225
242
|
}),
|
|
226
243
|
runtimeBus.on<Extract<TurnEvent, { type: 'TURN_CANCEL' }>>('TURN_CANCEL', (event) => {
|
|
227
244
|
promptContextReceipts.recordTurnOutcome({
|
|
@@ -231,7 +248,10 @@ export async function bootstrapRuntime(
|
|
|
231
248
|
stopReason: event.payload.stopReason,
|
|
232
249
|
detail: event.payload.reason,
|
|
233
250
|
});
|
|
234
|
-
if (activePromptTurnId === event.payload.turnId)
|
|
251
|
+
if (activePromptTurnId === event.payload.turnId) {
|
|
252
|
+
activePromptTurnId = null;
|
|
253
|
+
activePromptTurnText = null;
|
|
254
|
+
}
|
|
235
255
|
}),
|
|
236
256
|
);
|
|
237
257
|
const {
|
|
@@ -270,6 +290,7 @@ export async function bootstrapRuntime(
|
|
|
270
290
|
operatorPolicy: GOODVIBES_AGENT_OPERATOR_POLICY,
|
|
271
291
|
shellPaths: services.shellPaths,
|
|
272
292
|
memoryRegistry: services.memoryRegistry,
|
|
293
|
+
turnText: activePromptTurnText,
|
|
273
294
|
});
|
|
274
295
|
promptContextReceipts.record(composed.receipt);
|
|
275
296
|
return composed.prompt;
|
|
@@ -482,6 +503,27 @@ export async function bootstrapRuntime(
|
|
|
482
503
|
requestRender();
|
|
483
504
|
},
|
|
484
505
|
});
|
|
506
|
+
// W2A: probe the daemon spine OFF the interactive path. On a reachable daemon,
|
|
507
|
+
// fold the agent's own per-cwd legacy session store into it (once; marked
|
|
508
|
+
// migrated). Never starts the daemon; a down/absent daemon leaves the agent
|
|
509
|
+
// local-only with an honest offline status.
|
|
510
|
+
deferredStartup.schedule({
|
|
511
|
+
label: 'session-spine',
|
|
512
|
+
run: async () => {
|
|
513
|
+
const reachability = await services.sessionSpineClient.probeReachability();
|
|
514
|
+
if (reachability !== 'online') return;
|
|
515
|
+
foldLegacySpineStore(services.sessionSpineClient, {
|
|
516
|
+
storePath: services.shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'control-plane', 'sessions.json'),
|
|
517
|
+
markerPath: services.shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'control-plane', 'sessions.spine-folded.json'),
|
|
518
|
+
project: workingDir,
|
|
519
|
+
log: logger,
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
onError: (error) => {
|
|
523
|
+
logger.debug('Deferred session-spine startup failed', { error: summarizeError(error) });
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
|
|
485
527
|
const toolCount = toolRegistry.list().length;
|
|
486
528
|
conversation.splashOptions = {
|
|
487
529
|
workingDir,
|
|
@@ -492,8 +534,14 @@ export async function bootstrapRuntime(
|
|
|
492
534
|
|
|
493
535
|
// ── Phase 8: Background provider registration (non-blocking) ────────────
|
|
494
536
|
// These run after the initial render so they don't delay startup.
|
|
537
|
+
//
|
|
538
|
+
// Gated: the SDK's discovery pass probes every host on the local subnet
|
|
539
|
+
// unconditionally, so it is only ever invoked through runGatedLanScan,
|
|
540
|
+
// which requires an explicit, persisted consent decision first and reframes
|
|
541
|
+
// whatever the SDK emits into a single honest summary line. See
|
|
542
|
+
// src/runtime/lan-scan-consent.ts.
|
|
495
543
|
|
|
496
|
-
|
|
544
|
+
runGatedLanScan({
|
|
497
545
|
configManager,
|
|
498
546
|
providerRegistry,
|
|
499
547
|
runtime,
|
|
@@ -503,6 +551,17 @@ export async function bootstrapRuntime(
|
|
|
503
551
|
shellPaths: services.shellPaths,
|
|
504
552
|
surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
|
|
505
553
|
});
|
|
554
|
+
// W4-A9: refresh explicitly-subscribed external calendar feeds that are DUE
|
|
555
|
+
// per their own bounded interval (conditional 304 requests keep it cheap).
|
|
556
|
+
// Consent was given at subscribe time ("now and on each refresh"); with no
|
|
557
|
+
// subscriptions this touches nothing. Non-blocking — boot never waits on it;
|
|
558
|
+
// one honest aggregate line only when something refreshed or failed.
|
|
559
|
+
void scheduleCalendarSubscriptionBootRefresh({
|
|
560
|
+
shellPaths: services.shellPaths,
|
|
561
|
+
secretsManager: services.secretsManager,
|
|
562
|
+
systemMessageRouter,
|
|
563
|
+
requestRender,
|
|
564
|
+
});
|
|
506
565
|
const mcpDiscovery = scheduleBackgroundMcpDiscovery({
|
|
507
566
|
mcpRegistry: services.mcpRegistry,
|
|
508
567
|
systemMessageRouter,
|
|
@@ -597,6 +656,10 @@ export async function bootstrapRuntime(
|
|
|
597
656
|
commandRegistry,
|
|
598
657
|
systemMessageRouter,
|
|
599
658
|
shutdown: async (sessionData) => {
|
|
659
|
+
// W2A: best-effort spine close (short timeout, fire-and-forget) then stop
|
|
660
|
+
// the heartbeat timer. Tolerates a racing daemon stop; never blocks teardown.
|
|
661
|
+
services.sessionSpineClient.close(runtime.sessionId);
|
|
662
|
+
services.sessionSpineClient.dispose();
|
|
600
663
|
// Clear bootstrap-owned subscriptions
|
|
601
664
|
bootstrapUnsubs.forEach(fn => fn());
|
|
602
665
|
bootstrapUnsubs.length = 0;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar subscription boot refresh (W4-A9 completion).
|
|
3
|
+
*
|
|
4
|
+
* Fires once from bootstrap's Phase-8 area (after first render, like the gated
|
|
5
|
+
* LAN scan and MCP discovery), fully async and non-blocking: boot never waits on
|
|
6
|
+
* the network. Consent was given at subscribe time — the subscribe flow states
|
|
7
|
+
* "this will fetch the calendar from the URL you provided, now and on each
|
|
8
|
+
* refresh" — so refreshing already-subscribed feeds at boot is the promised
|
|
9
|
+
* behavior, not a silent new fetch. Feeds that were never subscribed are never
|
|
10
|
+
* touched; when there are no subscriptions this function does nothing at all
|
|
11
|
+
* (no network, no store write, no activity line).
|
|
12
|
+
*
|
|
13
|
+
* Bounded: only feeds actually DUE per their per-subscription refresh interval
|
|
14
|
+
* are fetched (`force: false` — the SDK store skips the rest without network),
|
|
15
|
+
* and due fetches send stored etag/last-modified validators so an unchanged
|
|
16
|
+
* feed costs one conditional 304 round trip.
|
|
17
|
+
*
|
|
18
|
+
* Honest reporting: ONE aggregate activity line, emitted only when something
|
|
19
|
+
* was actually checked or failed — e.g.
|
|
20
|
+
* [Calendar] checked 2 subscriptions (1 updated); 'work' unreachable — will retry next refresh
|
|
21
|
+
* A conditional-GET 304 ("not-modified") means the feed WAS checked but had
|
|
22
|
+
* nothing new — F6 fix: that no longer counts toward "updated", so the line
|
|
23
|
+
* never claims something changed when the round trip only confirmed nothing
|
|
24
|
+
* did. An all-skipped (nothing due) boot stays silent. Failures keep the
|
|
25
|
+
* last-good cached events, and /calendar subscriptions still shows the honest
|
|
26
|
+
* per-feed health (unreachable / parse-error / stale with age).
|
|
27
|
+
*/
|
|
28
|
+
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
29
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
30
|
+
import {
|
|
31
|
+
CalendarSubscriptionRegistry,
|
|
32
|
+
createHttpFeedFetcher,
|
|
33
|
+
type RefreshOutcome,
|
|
34
|
+
type SubscriptionSecretStore,
|
|
35
|
+
} from '../agent/calendar-subscription-registry.ts';
|
|
36
|
+
|
|
37
|
+
interface SystemMessageSinkLike {
|
|
38
|
+
low(message: string): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CalendarBootRefreshOptions {
|
|
42
|
+
readonly shellPaths: Pick<ShellPathService, 'resolveUserPath'>;
|
|
43
|
+
readonly secretsManager: SubscriptionSecretStore;
|
|
44
|
+
readonly systemMessageRouter: SystemMessageSinkLike;
|
|
45
|
+
readonly requestRender: () => void;
|
|
46
|
+
/** Injectable for tests — production callers omit it and get the real registry (real HTTP fetch). */
|
|
47
|
+
readonly buildRegistry?: () => CalendarSubscriptionRegistry;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build the single aggregate line from refresh outcomes, or null when nothing
|
|
52
|
+
* user-visible happened (everything skipped as not-due).
|
|
53
|
+
*
|
|
54
|
+
* F6 fix: a 304-not-modified round trip is a genuine check (it counts toward
|
|
55
|
+
* "checked N subscriptions"), but it is NOT a change — only outcome ===
|
|
56
|
+
* 'updated' counts toward "(M updated)". The old wording ("refreshed N")
|
|
57
|
+
* counted both the same, so an all-304 boot ("nothing changed anywhere")
|
|
58
|
+
* still read as "refreshed 2 subscriptions", which claims an update that
|
|
59
|
+
* never happened.
|
|
60
|
+
*/
|
|
61
|
+
export function formatCalendarBootRefreshLine(outcomes: readonly RefreshOutcome[]): string | null {
|
|
62
|
+
const checked = outcomes.filter((o) => o.outcome === 'updated' || o.outcome === 'not-modified');
|
|
63
|
+
const updated = checked.filter((o) => o.outcome === 'updated');
|
|
64
|
+
const failed = outcomes.filter((o) => o.outcome === 'unreachable' || o.outcome === 'parse-error');
|
|
65
|
+
if (checked.length === 0 && failed.length === 0) return null;
|
|
66
|
+
|
|
67
|
+
const parts: string[] = [];
|
|
68
|
+
if (checked.length > 0) {
|
|
69
|
+
parts.push(`checked ${checked.length} subscription${checked.length === 1 ? '' : 's'} (${updated.length} updated)`);
|
|
70
|
+
}
|
|
71
|
+
for (const f of failed) {
|
|
72
|
+
const label = f.outcome === 'unreachable' ? 'unreachable' : 'parse error';
|
|
73
|
+
parts.push(`'${f.name}' ${label} — will retry next refresh`);
|
|
74
|
+
}
|
|
75
|
+
return `[Calendar] ${parts.join('; ')}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Kick off the non-blocking boot refresh. Returns a promise that settles when
|
|
80
|
+
* the background pass finishes (bootstrap ignores it; tests await it). Never
|
|
81
|
+
* throws to the caller; failures are logged at debug and reported per-feed in
|
|
82
|
+
* the aggregate line.
|
|
83
|
+
*/
|
|
84
|
+
export function scheduleCalendarSubscriptionBootRefresh(options: CalendarBootRefreshOptions): Promise<void> {
|
|
85
|
+
const run = async (): Promise<void> => {
|
|
86
|
+
try {
|
|
87
|
+
const registry = options.buildRegistry
|
|
88
|
+
? options.buildRegistry()
|
|
89
|
+
: CalendarSubscriptionRegistry.create(options.shellPaths, options.secretsManager, createHttpFeedFetcher());
|
|
90
|
+
if (!registry.hasAny()) return; // nothing subscribed -> no network, no line
|
|
91
|
+
|
|
92
|
+
// force:false — only feeds due per their own bounded interval are fetched;
|
|
93
|
+
// the rest are skipped without touching the network.
|
|
94
|
+
const outcomes = await registry.refresh(undefined, { force: false });
|
|
95
|
+
const line = formatCalendarBootRefreshLine(outcomes);
|
|
96
|
+
if (line !== null) {
|
|
97
|
+
options.systemMessageRouter.low(line);
|
|
98
|
+
options.requestRender();
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.debug('Calendar subscription boot refresh failed', { error: summarizeError(error) });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return run();
|
|
105
|
+
}
|