@pellux/goodvibes-agent 1.8.0 → 1.8.2
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/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-44ar68xs.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-asvc2tv1.node} +0 -0
- package/dist/package/main.js +135434 -118764
- package/dist/package/{web-tree-sitter-vmqc5d26.wasm → web-tree-sitter-jbz042ba.wasm} +0 -0
- package/docs/tools-and-commands.md +9 -0
- package/package.json +3 -2
- package/src/agent/capability-registry.ts +300 -0
- package/src/agent/email/style-reply-lane.ts +13 -4
- package/src/agent/memory-consolidation-receipts.ts +79 -0
- package/src/agent/memory-prompt.ts +3 -1
- package/src/agent/operator-gateway-call.ts +134 -0
- package/src/agent/principal-attribution.ts +97 -0
- package/src/agent/prompt-context-receipts.ts +5 -1
- package/src/agent/unified-inbox.ts +17 -1
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/channel-profiles-command.ts +168 -0
- package/src/cli/ci-command.ts +214 -0
- package/src/cli/completion.ts +5 -0
- package/src/cli/connected-host-metrics.ts +165 -0
- package/src/cli/entrypoint.ts +25 -1
- package/src/cli/fleet-command.ts +178 -0
- package/src/cli/help.ts +115 -0
- package/src/cli/import-command.ts +208 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/management-commands.ts +9 -8
- package/src/cli/management.ts +42 -0
- package/src/cli/memory-command.ts +4 -3
- package/src/cli/openclaw-import.ts +382 -0
- package/src/cli/operator-command-args.ts +88 -0
- package/src/cli/parser.ts +12 -0
- package/src/cli/personas-command.ts +3 -2
- package/src/cli/principals-command.ts +230 -0
- package/src/cli/profiles-command.ts +2 -1
- package/src/cli/relay-command.ts +100 -0
- package/src/cli/routines-command.ts +4 -3
- package/src/cli/skill-bundle-command.ts +3 -2
- package/src/cli/skills-command.ts +3 -2
- package/src/cli/status.ts +114 -2
- package/src/cli/temporal-label.ts +89 -0
- package/src/cli/types.ts +7 -0
- package/src/cli/workspaces-command.ts +135 -0
- package/src/config/agent-settings-policy.ts +8 -0
- package/src/config/checkpoint-settings.ts +113 -0
- package/src/config/workspace-registration.ts +299 -0
- package/src/core/rewind-turn-anchors.ts +82 -0
- package/src/input/agent-workspace-categories.ts +1 -0
- package/src/input/agent-workspace-channel-triage.ts +27 -8
- package/src/input/agent-workspace-local-library-snapshot.ts +5 -1
- package/src/input/agent-workspace-onboarding-categories.ts +17 -1
- package/src/input/agent-workspace-snapshot.ts +25 -3
- package/src/input/agent-workspace-types.ts +5 -5
- package/src/input/commands/connected-host-admin-runtime.ts +75 -0
- package/src/input/commands.ts +2 -0
- package/src/input/settings-modal-types.ts +16 -5
- package/src/main.ts +17 -12
- package/src/permissions/approval-posture.ts +40 -5
- package/src/permissions/prompt.ts +35 -1
- package/src/renderer/settings-modal-helpers.ts +11 -0
- package/src/renderer/settings-modal.ts +21 -0
- package/src/renderer/terminal-escapes.ts +65 -12
- package/src/runtime/agent-runtime-events.ts +102 -3
- package/src/runtime/bootstrap-core.ts +111 -10
- package/src/runtime/bootstrap-external-services.ts +156 -0
- package/src/runtime/bootstrap.ts +73 -69
- package/src/runtime/context-accounting-source.ts +114 -0
- package/src/runtime/conversation-rewind-port.ts +133 -0
- package/src/runtime/index.ts +8 -28
- package/src/runtime/memory-consolidation-scheduler.ts +67 -0
- package/src/runtime/memory-consolidation-wiring.ts +56 -0
- package/src/runtime/memory-usage-wiring.ts +75 -0
- package/src/runtime/services.ts +257 -6
- package/src/runtime/tool-permission-safety.ts +18 -1
- package/src/shell/blocking-input.ts +38 -8
- package/src/shell/startup-wiring.ts +26 -1
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-harness-cli-command-policy.ts +29 -3
- package/src/tools/agent-harness-learning-auto-promote.ts +7 -3
- package/src/tools/agent-harness-memory-posture.ts +57 -2
- package/src/tools/agent-harness-metadata.ts +12 -0
- package/src/tools/agent-harness-mode-catalog.ts +1 -1
- package/src/tools/agent-harness-personal-ops-lanes.ts +8 -6
- package/src/tools/agent-harness-prompt-context.ts +3 -1
- package/src/tools/agent-harness-tool.ts +3 -2
- package/src/tools/agent-policy-explanation.ts +40 -0
- package/src/tools/tool-definition-compaction.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rewind-turn-anchors.ts — the session-scoped join key between the conversation
|
|
3
|
+
* and the workspace-checkpoint store, for message-anchored rewind.
|
|
4
|
+
*
|
|
5
|
+
* The unified rewind service (SDK platform/rewind) anchors a rewind to a
|
|
6
|
+
* `{ sessionId, turnId }`. Files-scope resolves that turnId against the
|
|
7
|
+
* workspace checkpoints the turn engine already stamps with the same turnId.
|
|
8
|
+
* Conversation-scope, however, needs to know HOW MANY conversation messages
|
|
9
|
+
* existed at that turn boundary — a mapping the SDK's checkpoint store does not
|
|
10
|
+
* carry. This registry supplies exactly that mapping: at every TURN_COMPLETED
|
|
11
|
+
* this Agent records the turnId together with the live
|
|
12
|
+
* `conversation.getMessageCount()`, so a later rewind to that turnId can
|
|
13
|
+
* truncate the conversation to precisely the boundary the files checkpoint
|
|
14
|
+
* captured.
|
|
15
|
+
*
|
|
16
|
+
* Ported from goodvibes-tui's src/core/rewind-turn-anchors.ts (same join-key
|
|
17
|
+
* shape, same TURN_COMPLETED wiring point) so the two front-ends' rewind
|
|
18
|
+
* semantics cannot drift.
|
|
19
|
+
*
|
|
20
|
+
* Scope + lifetime: in-memory, keyed by sessionId, populated for the CURRENT
|
|
21
|
+
* process run only. Turns from a resumed prior session (whose turnIds predate
|
|
22
|
+
* this run) are not present — an honest limitation: a rewind to an
|
|
23
|
+
* unrecorded turnId gets exactly the anchors this run recorded, never an
|
|
24
|
+
* invented boundary.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** One recorded turn boundary — the rewind coordinator's per-turn anchor. */
|
|
28
|
+
export interface TurnAnchor {
|
|
29
|
+
/** The turn engine's turn id, shared with the workspace checkpoint's `turnId`. */
|
|
30
|
+
readonly turnId: string;
|
|
31
|
+
/** A short human label (the truncated user prompt) for the recent-turns picker. */
|
|
32
|
+
readonly label: string;
|
|
33
|
+
/** `conversation.getMessageCount()` captured at this turn's completion — the conversation truncation boundary. */
|
|
34
|
+
readonly messageCount: number;
|
|
35
|
+
/** Wall-clock ms at capture, for ordering + age display. */
|
|
36
|
+
readonly at: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Hard cap so a very long session cannot grow this registry without bound. */
|
|
40
|
+
const MAX_ANCHORS_PER_SESSION = 500;
|
|
41
|
+
|
|
42
|
+
const registry = new Map<string, TurnAnchor[]>();
|
|
43
|
+
|
|
44
|
+
/** Trim a user prompt to a single compact line for the picker. */
|
|
45
|
+
export function summarizeTurnLabel(text: string | null | undefined, max = 72): string {
|
|
46
|
+
const oneLine = (text ?? '').replace(/\s+/g, ' ').trim();
|
|
47
|
+
if (oneLine.length === 0) return '(no prompt text)';
|
|
48
|
+
return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Record a completed turn's anchor. Idempotent per turnId: a repeated turnId
|
|
53
|
+
* updates the existing entry in place (the checkpoint engine can re-snapshot a
|
|
54
|
+
* turn) rather than duplicating it.
|
|
55
|
+
*/
|
|
56
|
+
export function recordTurnAnchor(sessionId: string, anchor: TurnAnchor): void {
|
|
57
|
+
if (!sessionId || !anchor.turnId) return;
|
|
58
|
+
const list = registry.get(sessionId) ?? [];
|
|
59
|
+
const existingIndex = list.findIndex((a) => a.turnId === anchor.turnId);
|
|
60
|
+
if (existingIndex >= 0) {
|
|
61
|
+
list[existingIndex] = anchor;
|
|
62
|
+
} else {
|
|
63
|
+
list.push(anchor);
|
|
64
|
+
if (list.length > MAX_ANCHORS_PER_SESSION) list.splice(0, list.length - MAX_ANCHORS_PER_SESSION);
|
|
65
|
+
}
|
|
66
|
+
registry.set(sessionId, list);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** All recorded anchors for a session, oldest first (chronological). */
|
|
70
|
+
export function getTurnAnchors(sessionId: string): readonly TurnAnchor[] {
|
|
71
|
+
return registry.get(sessionId) ?? [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Resolve an anchor by exact turnId, or null when this run never recorded it. */
|
|
75
|
+
export function resolveTurnAnchor(sessionId: string, turnId: string): TurnAnchor | null {
|
|
76
|
+
return registry.get(sessionId)?.find((a) => a.turnId === turnId) ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Drop a session's anchors. Exposed for tests and session reset. */
|
|
80
|
+
export function clearTurnAnchors(sessionId: string): void {
|
|
81
|
+
registry.delete(sessionId);
|
|
82
|
+
}
|
|
@@ -489,6 +489,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
489
489
|
{ id: 'tasks-filter', label: 'Filter host tasks', detail: 'Open a status/kind form for read-only connected-host task filtering.', editorKind: 'task-list-filter', kind: 'editor', safety: 'read-only' },
|
|
490
490
|
{ id: 'task-show', label: 'Inspect host task', detail: 'Open a task-id form for read-only connected-host task metadata.', editorKind: 'task-show', kind: 'editor', safety: 'read-only' },
|
|
491
491
|
{ id: 'task-output', label: 'Show task output', detail: 'Open a task-id form for read-only connected-host task output.', editorKind: 'task-output', kind: 'editor', safety: 'read-only' },
|
|
492
|
+
{ id: 'ci-watches-list', label: 'CI watches', detail: 'List standing CI watches on the connected host. Use /ci status <repo> for a per-job conclusion report, and /ci watches create ... --yes to add a watch with channel delivery.', command: '/ci watches list', kind: 'command', safety: 'read-only' },
|
|
492
493
|
{ id: 'approval-review', label: 'Review approval class', detail: 'Open an approval-kind form for read-only review of one approval class.', editorKind: 'approval-review', kind: 'editor', safety: 'read-only' },
|
|
493
494
|
{ id: 'approval-approve', label: 'Approve request', detail: 'Open a confirmed form for approving one pending connected-host approval request by id.', editorKind: 'approval-approve', kind: 'editor', safety: 'safe' },
|
|
494
495
|
{ id: 'approval-deny', label: 'Deny request', detail: 'Open a confirmed form for denying one pending connected-host approval request by id.', editorKind: 'approval-deny', kind: 'editor', safety: 'safe' },
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { formatAgentChannelDeliveryReceiptLine, readAgentChannelDeliveryReceipts } from '../agent/channel-delivery-receipts.ts';
|
|
3
3
|
import type { CommandContext } from './command-registry.ts';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
fetchConnectedHostReadOnlyRoute,
|
|
6
|
+
resolveConnectedHostConnection,
|
|
7
|
+
type ConnectedHostConnection,
|
|
8
|
+
type ConnectedHostRouteFailure,
|
|
9
|
+
type ConnectedHostRouteResult,
|
|
10
|
+
} from './connected-host-routes.ts';
|
|
5
11
|
import type { AgentWorkspaceChannelStatus } from './agent-workspace-channels.ts';
|
|
6
12
|
import { buildAgentWorkspaceChannels } from './agent-workspace-channels.ts';
|
|
13
|
+
import { resolveChannelPrincipalAttribution, UNKNOWN_PRINCIPAL_LABEL } from '../agent/principal-attribution.ts';
|
|
7
14
|
|
|
8
15
|
type JsonRecord = Record<string, unknown>;
|
|
9
16
|
|
|
@@ -249,15 +256,20 @@ function surfaceMessageTriage(result: ConnectedHostRouteResult, limit: number):
|
|
|
249
256
|
};
|
|
250
257
|
}
|
|
251
258
|
|
|
252
|
-
function routeBindingView(binding: JsonRecord): Record<string, unknown
|
|
259
|
+
async function routeBindingView(binding: JsonRecord, connection: ConnectedHostConnection): Promise<Record<string, unknown>> {
|
|
253
260
|
const externalId = readString(binding, 'externalId');
|
|
261
|
+
const surfaceKind = readString(binding, 'surfaceKind', 'unknown');
|
|
262
|
+
const attribution = await resolveChannelPrincipalAttribution(connection, surfaceKind, externalId);
|
|
254
263
|
return {
|
|
255
264
|
id: readString(binding, 'id', 'binding'),
|
|
256
265
|
kind: readString(binding, 'kind', 'unknown'),
|
|
257
|
-
surfaceKind
|
|
266
|
+
surfaceKind,
|
|
258
267
|
surfaceId: readString(binding, 'surfaceId') || null,
|
|
259
268
|
title: redactText(readString(binding, 'title'), 100) || null,
|
|
260
269
|
externalIdDigest: externalId ? `sha256:${digest(externalId)}` : null,
|
|
270
|
+
principal: attribution.principal,
|
|
271
|
+
principalLabel: attribution.label,
|
|
272
|
+
...(attribution.error ? { principalResolutionError: attribution.error } : {}),
|
|
261
273
|
sessionPolicy: readString(binding, 'sessionPolicy') || null,
|
|
262
274
|
threadPolicy: readString(binding, 'threadPolicy') || null,
|
|
263
275
|
deliveryGuarantee: readString(binding, 'deliveryGuarantee') || null,
|
|
@@ -268,7 +280,11 @@ function routeBindingView(binding: JsonRecord): Record<string, unknown> {
|
|
|
268
280
|
};
|
|
269
281
|
}
|
|
270
282
|
|
|
271
|
-
function routeBindingTriage(
|
|
283
|
+
async function routeBindingTriage(
|
|
284
|
+
result: ConnectedHostRouteResult,
|
|
285
|
+
limit: number,
|
|
286
|
+
connection: ConnectedHostConnection,
|
|
287
|
+
): Promise<Record<string, unknown>> {
|
|
272
288
|
if (!result.ok) {
|
|
273
289
|
return {
|
|
274
290
|
route: result.route,
|
|
@@ -280,12 +296,14 @@ function routeBindingTriage(result: ConnectedHostRouteResult, limit: number): Re
|
|
|
280
296
|
}
|
|
281
297
|
const root = isRecord(result.body) ? result.body : {};
|
|
282
298
|
const bindings = readRecordArray(root, 'bindings');
|
|
299
|
+
const sliced = bindings.slice(0, limit);
|
|
300
|
+
const bindingViews = await Promise.all(sliced.map((binding) => routeBindingView(binding, connection)));
|
|
283
301
|
return {
|
|
284
302
|
route: result.route,
|
|
285
303
|
state: routeState(result, bindings.length === 0),
|
|
286
304
|
totalBindings: bindings.length,
|
|
287
|
-
bindings:
|
|
288
|
-
policy:
|
|
305
|
+
bindings: bindingViews,
|
|
306
|
+
policy: `Read-only route continuity. External ids are digested so channel identifiers are not printed into chat. Sender attribution shows the resolved principal when the channel identity maps to one, and "${UNKNOWN_PRINCIPAL_LABEL}" otherwise.`,
|
|
289
307
|
};
|
|
290
308
|
}
|
|
291
309
|
|
|
@@ -338,6 +356,7 @@ export async function buildAgentWorkspaceChannelTriage(
|
|
|
338
356
|
): Promise<AgentWorkspaceChannelTriage> {
|
|
339
357
|
const limit = readLimit(args.limit, 12);
|
|
340
358
|
const channels = buildAgentWorkspaceChannels(context);
|
|
359
|
+
const connection = resolveConnectedHostConnection(context);
|
|
341
360
|
const [deliveriesResult, messagesResult, bindingsResult] = await Promise.all([
|
|
342
361
|
fetchConnectedHostReadOnlyRoute(context, '/api/deliveries'),
|
|
343
362
|
fetchConnectedHostReadOnlyRoute(context, '/api/control-plane/messages'),
|
|
@@ -346,7 +365,7 @@ export async function buildAgentWorkspaceChannelTriage(
|
|
|
346
365
|
const readiness = channelTriageReadiness(channels, limit);
|
|
347
366
|
const deliveries = deliveryTriage(deliveriesResult, limit);
|
|
348
367
|
const surfaceMessages = surfaceMessageTriage(messagesResult, limit);
|
|
349
|
-
const routeBindings = routeBindingTriage(bindingsResult, limit);
|
|
368
|
+
const routeBindings = await routeBindingTriage(bindingsResult, limit, connection);
|
|
350
369
|
const receipts = receiptTriage(context, limit);
|
|
351
370
|
const routeFailures = routeFailureCount([deliveriesResult, messagesResult, bindingsResult]);
|
|
352
371
|
const readinessAttention = typeof readiness.attention === 'number' ? readiness.attention : 0;
|
|
@@ -457,7 +476,7 @@ export function formatAgentWorkspaceChannelTriage(triage: AgentWorkspaceChannelT
|
|
|
457
476
|
lines.push(` state: ${bindings.state}; total: ${bindings.totalBindings}`);
|
|
458
477
|
const routeRows = Array.isArray(bindings.bindings) ? bindings.bindings.filter(isRecord) : [];
|
|
459
478
|
for (const binding of routeRows.slice(0, 5)) {
|
|
460
|
-
lines.push(` - ${readString(binding, 'id')}: ${readString(binding, 'surfaceKind')} ${readString(binding, 'kind')} external=${readString(binding, 'externalIdDigest')}`);
|
|
479
|
+
lines.push(` - ${readString(binding, 'id')}: ${readString(binding, 'surfaceKind')} ${readString(binding, 'kind')} external=${readString(binding, 'externalIdDigest')} sender=${readString(binding, 'principalLabel', UNKNOWN_PRINCIPAL_LABEL)}`);
|
|
461
480
|
}
|
|
462
481
|
if (routeRows.length === 0) lines.push(' no route bindings reported');
|
|
463
482
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { memoryRecordTemporalStatus, type MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
|
|
2
2
|
import type { AgentNoteRecord } from '../agent/note-registry.ts';
|
|
3
3
|
import type { AgentPersonaRecord } from '../agent/persona-registry.ts';
|
|
4
4
|
import { formatAgentRecordOrigin } from '../agent/record-labels.ts';
|
|
@@ -125,6 +125,10 @@ export function summarizeMemoryItem(record: MemoryRecord): AgentWorkspaceLocalLi
|
|
|
125
125
|
scope: record.scope,
|
|
126
126
|
cls: record.cls,
|
|
127
127
|
confidence: record.confidence,
|
|
128
|
+
// Visible expiry: a record outside its [validFrom, validUntil) window is
|
|
129
|
+
// still stored (not deleted) but not prompt-injected — see
|
|
130
|
+
// memoryRecordTemporalStatus / describeMemoryPromptEligibility.
|
|
131
|
+
temporalStatus: memoryRecordTemporalStatus(record),
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AgentWorkspaceCategory } from './agent-workspace-types.ts';
|
|
2
2
|
import { settingAction } from './agent-workspace-category-actions.ts';
|
|
3
|
+
import { renderCapabilityReadinessLine } from '../agent/capability-registry.ts';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const ONBOARDING_DETAIL_CATEGORIES_BASE: readonly AgentWorkspaceCategory[] = [
|
|
5
6
|
{
|
|
6
7
|
id: 'onboarding-channels',
|
|
7
8
|
group: 'ONBOARDING',
|
|
@@ -80,6 +81,8 @@ export const AGENT_WORKSPACE_ONBOARDING_DETAIL_CATEGORIES: readonly AgentWorkspa
|
|
|
80
81
|
{ id: 'notification-remove-webhook', label: 'Remove notification target', detail: 'Remove one webhook notification target after confirmation.', editorKind: 'notify-webhook-remove', kind: 'editor', safety: 'safe' },
|
|
81
82
|
{ id: 'notification-clear-webhooks', label: 'Clear notification targets', detail: 'Remove every configured webhook notification target after confirmation.', editorKind: 'notify-webhook-clear', kind: 'editor', safety: 'safe' },
|
|
82
83
|
{ id: 'notification-test-webhooks', label: 'Test notification targets', detail: 'Send one test notification to configured targets after typed confirmation.', editorKind: 'notify-webhook-test', kind: 'editor', safety: 'safe' },
|
|
84
|
+
{ id: 'principals-list', label: 'People & identities', detail: 'List the cross-channel principal registry: who each channel identity (Slack member, Telegram chat, ...) resolves to. Unmapped senders show as "unknown principal" in session attribution; use /principals create ... --yes to map them.', command: '/principals list', kind: 'command', safety: 'read-only' },
|
|
85
|
+
{ id: 'channel-profiles-list', label: 'Channel session profiles', detail: 'List per-channel session profile bindings: the model, provider, and permission mode sessions from each channel start with. Use /channel-profiles set <surface-kind> ... --yes to change one.', command: '/channel-profiles list', kind: 'command', safety: 'read-only' },
|
|
83
86
|
{ id: 'channel-safety', label: 'Delivery safety', detail: 'Messages only go out when you explicitly send them. Nothing is delivered silently from this page.', kind: 'guidance', safety: 'blocked' },
|
|
84
87
|
],
|
|
85
88
|
},
|
|
@@ -142,3 +145,16 @@ export const AGENT_WORKSPACE_ONBOARDING_DETAIL_CATEGORIES: readonly AgentWorkspa
|
|
|
142
145
|
],
|
|
143
146
|
},
|
|
144
147
|
];
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Each onboarding category shows the readiness of the capabilities it
|
|
151
|
+
* advertises, appended to its detail and sourced entirely from the capability
|
|
152
|
+
* registry. No category hand-writes a maturity claim; the registry is the one
|
|
153
|
+
* source of truth, so onboarding copy and the enforcement tests can never
|
|
154
|
+
* drift from a capability's declared level.
|
|
155
|
+
*/
|
|
156
|
+
export const AGENT_WORKSPACE_ONBOARDING_DETAIL_CATEGORIES: readonly AgentWorkspaceCategory[] =
|
|
157
|
+
ONBOARDING_DETAIL_CATEGORIES_BASE.map((category) => {
|
|
158
|
+
const readinessLine = renderCapabilityReadinessLine(category.id);
|
|
159
|
+
return readinessLine ? { ...category, detail: `${category.detail} ${readinessLine}` } : category;
|
|
160
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { basename, sep } from 'node:path';
|
|
2
2
|
import type { CommandContext } from './command-registry.ts';
|
|
3
3
|
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
4
|
+
import { memoryRecordTemporalStatus } from '@pellux/goodvibes-sdk/platform/state';
|
|
4
5
|
import { describeMemoryPromptEligibility, isPromptActiveMemory } from '../agent/memory-prompt.ts';
|
|
5
6
|
import { summarizeAgentBehaviorDiscovery } from '../agent/behavior-discovery-summary.ts';
|
|
6
7
|
import { buildSetupWizardDurableReceipts } from '../agent/setup-wizard-artifact-receipts.ts';
|
|
@@ -56,6 +57,10 @@ export interface AgentWorkspaceLiveMemoryCounters {
|
|
|
56
57
|
readonly count: number;
|
|
57
58
|
readonly reviewQueueCount: number;
|
|
58
59
|
readonly promptActiveCount: number;
|
|
60
|
+
/** Stored but not yet valid (now < validFrom). Still counted in `count`. */
|
|
61
|
+
readonly temporallyPendingCount: number;
|
|
62
|
+
/** Stored but past validUntil. Still counted in `count`, never deleted. */
|
|
63
|
+
readonly temporallyExpiredCount: number;
|
|
59
64
|
readonly items: readonly AgentWorkspaceLocalLibraryItem[];
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -78,12 +83,27 @@ export interface AgentWorkspaceLiveMemoryCounters {
|
|
|
78
83
|
*/
|
|
79
84
|
export function readLiveAgentMemoryCounters(context: CommandContext): AgentWorkspaceLiveMemoryCounters {
|
|
80
85
|
const memory = context.clients?.agentKnowledgeApi?.memory;
|
|
81
|
-
if (!memory)
|
|
86
|
+
if (!memory) {
|
|
87
|
+
return { count: 0, reviewQueueCount: 0, promptActiveCount: 0, temporallyPendingCount: 0, temporallyExpiredCount: 0, items: [] };
|
|
88
|
+
}
|
|
82
89
|
const records = [...memory.getAll()].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
90
|
+
let temporallyPendingCount = 0;
|
|
91
|
+
let temporallyExpiredCount = 0;
|
|
92
|
+
for (const record of records) {
|
|
93
|
+
const temporalStatus = memoryRecordTemporalStatus(record);
|
|
94
|
+
if (temporalStatus === 'pending') temporallyPendingCount += 1;
|
|
95
|
+
else if (temporalStatus === 'expired') temporallyExpiredCount += 1;
|
|
96
|
+
}
|
|
83
97
|
return {
|
|
84
98
|
count: records.length,
|
|
85
99
|
reviewQueueCount: memory.reviewQueue(100).length,
|
|
86
|
-
|
|
100
|
+
// Bound to one arg — a bare `.filter(isPromptActiveMemory)` leaks the
|
|
101
|
+
// array index in as Array.filter's second (index) argument, which lands
|
|
102
|
+
// in isPromptActiveMemory's `now` param and silently breaks the
|
|
103
|
+
// temporal-validity check for every record past index 0.
|
|
104
|
+
promptActiveCount: records.filter((record) => isPromptActiveMemory(record)).length,
|
|
105
|
+
temporallyPendingCount,
|
|
106
|
+
temporallyExpiredCount,
|
|
87
107
|
// Each item carries the honest, per-record eligibility reason straight from
|
|
88
108
|
// describeMemoryPromptEligibility — the same wording source
|
|
89
109
|
// prompt-context-receipts.ts and agent-harness-prompt-context.ts use for prompt
|
|
@@ -104,7 +124,7 @@ export function buildAgentWorkspaceMemorySnapshot(context: CommandContext): Agen
|
|
|
104
124
|
try {
|
|
105
125
|
return readLiveAgentMemoryCounters(context);
|
|
106
126
|
} catch {
|
|
107
|
-
return { count: 0, reviewQueueCount: 0, promptActiveCount: 0, items: [] };
|
|
127
|
+
return { count: 0, reviewQueueCount: 0, promptActiveCount: 0, temporallyPendingCount: 0, temporallyExpiredCount: 0, items: [] };
|
|
108
128
|
}
|
|
109
129
|
}
|
|
110
130
|
|
|
@@ -326,6 +346,8 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
326
346
|
localMemoryCount: memorySnapshot.count,
|
|
327
347
|
localMemoryReviewQueueCount: memorySnapshot.reviewQueueCount,
|
|
328
348
|
localMemoryPromptActiveCount: memorySnapshot.promptActiveCount,
|
|
349
|
+
localMemoryTemporallyPendingCount: memorySnapshot.temporallyPendingCount,
|
|
350
|
+
localMemoryTemporallyExpiredCount: memorySnapshot.temporallyExpiredCount,
|
|
329
351
|
localMemories: memorySnapshot.items,
|
|
330
352
|
localNoteCount: noteSnapshot.count,
|
|
331
353
|
localNoteReviewQueueCount: noteSnapshot.reviewQueueCount,
|
|
@@ -370,13 +370,11 @@ export interface AgentWorkspaceLocalLibraryItem {
|
|
|
370
370
|
readonly missingRequirementCount?: number;
|
|
371
371
|
readonly missingRequirements?: readonly string[];
|
|
372
372
|
readonly startCount?: number;
|
|
373
|
-
/** Memory-only: whether this record currently clears the prompt-injection
|
|
374
|
-
* recall floor, per describeMemoryPromptEligibility. */
|
|
373
|
+
/** Memory-only: whether this record currently clears the prompt-injection recall floor, per describeMemoryPromptEligibility. */
|
|
375
374
|
readonly promptEligible?: boolean;
|
|
376
|
-
/** Memory-only: the honest, per-record reason from describeMemoryPromptEligibility —
|
|
377
|
-
* the same wording source prompt-context-receipts.ts and agent-harness-prompt-context.ts
|
|
378
|
-
* use, so this workspace snapshot never has to invent its own paraphrase. */
|
|
375
|
+
/** Memory-only: the honest, per-record reason from describeMemoryPromptEligibility — the same wording source prompt-context-receipts.ts and agent-harness-prompt-context.ts use. */
|
|
379
376
|
readonly promptEligibilityReason?: string;
|
|
377
|
+
readonly temporalStatus?: 'active' | 'pending' | 'expired'; // memoryRecordTemporalStatus; pending/expired stay stored, just not prompt-injected
|
|
380
378
|
}
|
|
381
379
|
|
|
382
380
|
export interface AgentWorkspaceRecentReviewerHandoffArtifact {
|
|
@@ -699,6 +697,8 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
699
697
|
readonly localMemoryCount: number;
|
|
700
698
|
readonly localMemoryReviewQueueCount: number;
|
|
701
699
|
readonly localMemoryPromptActiveCount: number;
|
|
700
|
+
readonly localMemoryTemporallyPendingCount: number;
|
|
701
|
+
readonly localMemoryTemporallyExpiredCount: number;
|
|
702
702
|
readonly localMemories: readonly AgentWorkspaceLocalLibraryItem[];
|
|
703
703
|
readonly localNoteCount: number;
|
|
704
704
|
readonly localNoteReviewQueueCount: number;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI slash commands for the connected host's CI-watch, principal registry,
|
|
3
|
+
* and per-channel profile admin families: /ci, /principals, /channel-profiles.
|
|
4
|
+
*
|
|
5
|
+
* These are thin bridges onto the exact same CLI handlers the terminal
|
|
6
|
+
* commands use (src/cli/ci-command.ts, principals-command.ts,
|
|
7
|
+
* channel-profiles-command.ts): the TUI handler re-parses the invocation
|
|
8
|
+
* through the real CLI parser and hands the result to the CLI handler, so
|
|
9
|
+
* subcommand grammar, --yes confirmation gates, per-job CI conclusion
|
|
10
|
+
* rendering, and connected-host failure classification can never drift
|
|
11
|
+
* between the two surfaces. Output prints into the transcript verbatim.
|
|
12
|
+
*/
|
|
13
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
14
|
+
import { parseGoodVibesCli } from '../../cli/parser.ts';
|
|
15
|
+
import type { CliCommandOutput } from '../../cli/types.ts';
|
|
16
|
+
import type { CliCommandRuntime } from '../../cli/management.ts';
|
|
17
|
+
import { handleCiCommand } from '../../cli/ci-command.ts';
|
|
18
|
+
import { handlePrincipalsCommand } from '../../cli/principals-command.ts';
|
|
19
|
+
import { handleChannelProfilesCommand } from '../../cli/channel-profiles-command.ts';
|
|
20
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
21
|
+
|
|
22
|
+
function buildCliRuntime(ctx: CommandContext, command: string, args: readonly string[]): CliCommandRuntime {
|
|
23
|
+
const shellPaths = requireShellPaths(ctx);
|
|
24
|
+
return {
|
|
25
|
+
cli: parseGoodVibesCli([command, ...args]),
|
|
26
|
+
configManager: ctx.platform.configManager,
|
|
27
|
+
workingDirectory: shellPaths.workingDirectory,
|
|
28
|
+
homeDirectory: shellPaths.homeDirectory,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function runBridgedCliCommand(
|
|
33
|
+
ctx: CommandContext,
|
|
34
|
+
command: string,
|
|
35
|
+
args: readonly string[],
|
|
36
|
+
handler: (runtime: CliCommandRuntime) => Promise<CliCommandOutput>,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const result = await handler(buildCliRuntime(ctx, command, args));
|
|
39
|
+
ctx.print(result.output);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function registerConnectedHostAdminCommands(registry: CommandRegistry): void {
|
|
43
|
+
registry.register({
|
|
44
|
+
name: 'ci',
|
|
45
|
+
description: 'Check a repo/PR CI status with per-job conclusions and manage standing CI watches on the connected host',
|
|
46
|
+
hidden: true,
|
|
47
|
+
usage: 'status <repo> [--ref <ref>] [--pr <number>] | watches list | watches create <repo> --delivery-channel <channel> [--trigger-fix-session] --yes | watches run <watch-id> | watches delete <watch-id> --yes',
|
|
48
|
+
argsHint: 'status <repo> | watches list',
|
|
49
|
+
async handler(args, ctx) {
|
|
50
|
+
await runBridgedCliCommand(ctx, 'ci', args, handleCiCommand);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
registry.register({
|
|
54
|
+
name: 'principals',
|
|
55
|
+
aliases: ['principal'],
|
|
56
|
+
description: 'Manage the connected host cross-channel principal identity registry',
|
|
57
|
+
hidden: true,
|
|
58
|
+
usage: 'list | get <id> | resolve --channel <channel> --value <value> | create --name <name> --kind <user|bot|service|token> [--identity channel:value] --yes | update <id> [...] --yes | delete <id> --yes',
|
|
59
|
+
argsHint: 'list | resolve --channel slack --value U123',
|
|
60
|
+
async handler(args, ctx) {
|
|
61
|
+
await runBridgedCliCommand(ctx, 'principals', args, handlePrincipalsCommand);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
registry.register({
|
|
65
|
+
name: 'channel-profiles',
|
|
66
|
+
aliases: ['channel-profile'],
|
|
67
|
+
description: 'Manage per-channel session profile bindings (model, provider, permission mode) on the connected host',
|
|
68
|
+
hidden: true,
|
|
69
|
+
usage: 'list | get <surface-kind> [--channel-id <id>] | set <surface-kind> [--model <model>] [--provider <provider>] [--permission-mode <mode>] --yes | delete <surface-kind> --yes',
|
|
70
|
+
argsHint: 'list | get slack',
|
|
71
|
+
async handler(args, ctx) {
|
|
72
|
+
await runBridgedCliCommand(ctx, 'channel-profiles', args, handleChannelProfilesCommand);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
package/src/input/commands.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { registerBriefRuntimeCommands } from './commands/brief-runtime.ts';
|
|
|
38
38
|
import { registerSupportBundleRuntimeCommands } from './commands/support-bundle-runtime.ts';
|
|
39
39
|
import { registerCompatRuntimeCommands } from './commands/compat-runtime.ts';
|
|
40
40
|
import { registerOperatorActionRuntimeCommands } from './commands/operator-actions-runtime.ts';
|
|
41
|
+
import { registerConnectedHostAdminCommands } from './commands/connected-host-admin-runtime.ts';
|
|
41
42
|
import { registerEmailRuntimeCommands } from './commands/email-runtime.ts';
|
|
42
43
|
import { registerCalendarRuntimeCommands } from './commands/calendar-runtime.ts';
|
|
43
44
|
|
|
@@ -65,6 +66,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
65
66
|
registerSupportBundleRuntimeCommands(registry);
|
|
66
67
|
registerCompatRuntimeCommands(registry);
|
|
67
68
|
registerOperatorActionRuntimeCommands(registry);
|
|
69
|
+
registerConnectedHostAdminCommands(registry);
|
|
68
70
|
registerCalendarRuntimeCommands(registry);
|
|
69
71
|
registerAgentRuntimeProfileRuntimeCommands(registry);
|
|
70
72
|
registerVibeRuntimeCommands(registry);
|
|
@@ -27,16 +27,19 @@ export type SettingsCategory =
|
|
|
27
27
|
| 'behavior'
|
|
28
28
|
| 'storage'
|
|
29
29
|
| 'permissions'
|
|
30
|
+
| 'diagnostics'
|
|
30
31
|
| 'tools'
|
|
31
32
|
| 'helper'
|
|
32
33
|
| 'tts'
|
|
33
34
|
| 'surfaces'
|
|
34
35
|
| 'automation'
|
|
36
|
+
| 'checkin'
|
|
35
37
|
| 'service'
|
|
36
38
|
| 'controlPlane'
|
|
37
39
|
| 'httpListener'
|
|
38
40
|
| 'web'
|
|
39
41
|
| 'watchers'
|
|
42
|
+
| 'relay'
|
|
40
43
|
| 'network'
|
|
41
44
|
| 'orchestration'
|
|
42
45
|
| 'planner'
|
|
@@ -50,7 +53,15 @@ export type SettingsCategory =
|
|
|
50
53
|
| 'mcp'
|
|
51
54
|
| 'flags'
|
|
52
55
|
| 'release'
|
|
53
|
-
| 'daemon'
|
|
56
|
+
| 'daemon'
|
|
57
|
+
| 'atRest'
|
|
58
|
+
| 'learning'
|
|
59
|
+
| 'agents'
|
|
60
|
+
| 'notifications'
|
|
61
|
+
| 'policy'
|
|
62
|
+
| 'fetch'
|
|
63
|
+
| 'security'
|
|
64
|
+
| 'integrations';
|
|
54
65
|
|
|
55
66
|
export type SettingsFocusPane = 'categories' | 'settings';
|
|
56
67
|
|
|
@@ -58,11 +69,11 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
|
|
|
58
69
|
readonly label: string;
|
|
59
70
|
readonly categories: readonly SettingsCategory[];
|
|
60
71
|
}> = [
|
|
61
|
-
{ label: 'Agent Experience', categories: ['display', 'ui', 'behavior', 'permissions'] },
|
|
72
|
+
{ label: 'Agent Experience', categories: ['display', 'ui', 'behavior', 'agents', 'notifications', 'permissions', 'policy', 'fetch', 'diagnostics'] },
|
|
62
73
|
{ label: 'Models and Providers', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts'] },
|
|
63
|
-
{ label: 'Agent-local state', categories: ['storage', 'cache', 'telemetry'] },
|
|
64
|
-
{ label: 'Channels and Tools', categories: ['surfaces', 'mcp', 'automation'] },
|
|
65
|
-
{ label: 'Daemon Runtime', categories: ['daemon', 'service', 'controlPlane', 'httpListener', 'web', 'watchers', 'network'] },
|
|
74
|
+
{ label: 'Agent-local state', categories: ['storage', 'cache', 'telemetry', 'atRest', 'security', 'learning'] },
|
|
75
|
+
{ label: 'Channels and Tools', categories: ['surfaces', 'mcp', 'automation', 'checkin', 'integrations'] },
|
|
76
|
+
{ label: 'Daemon Runtime', categories: ['daemon', 'service', 'controlPlane', 'httpListener', 'web', 'watchers', 'network', 'relay'] },
|
|
66
77
|
{ label: 'Advanced Runtime', categories: ['orchestration', 'planner', 'runtime', 'sandbox', 'batch', 'cloudflare', 'wrfc'] },
|
|
67
78
|
{ label: 'Advanced', categories: ['flags', 'release'] },
|
|
68
79
|
];
|
package/src/main.ts
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
loadRecoveryConversation,
|
|
42
42
|
} from '@/runtime/index.ts';
|
|
43
43
|
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
44
|
-
import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
|
|
44
|
+
import { handleBlockingShellInput, type PendingPermissionState, type PendingWorkspaceRegistrationState } from './shell/blocking-input.ts';
|
|
45
45
|
import { createAgentWorkspaceFullscreenComposite, createFullscreenCompositeFromLines } from './shell/agent-workspace-fullscreen.ts';
|
|
46
46
|
import { getTerminalSize } from './shell/terminal-size.ts';
|
|
47
47
|
import { buildShellSessionContinuityHints } from './shell/session-continuity-hints.ts';
|
|
@@ -49,7 +49,8 @@ import { wireShellUiOpeners } from './shell/ui-openers.ts';
|
|
|
49
49
|
import { deriveComposerState } from './core/composer-state.ts';
|
|
50
50
|
import { buildPersistedSessionContext } from '@/runtime/index.ts';
|
|
51
51
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
52
|
-
import {
|
|
52
|
+
import { installFocusModeExitGuard, markFocusModeEnabled, wrapRequestPermissionWithApprovalAlert } from './shell/terminal-focus-mode.ts';
|
|
53
|
+
import { CLEAR_VIEWPORT_HOME, buildEnterSequence, buildExitSequence } from './renderer/terminal-escapes.ts';
|
|
53
54
|
import { prepareShellCliRuntime } from './cli/entrypoint.ts';
|
|
54
55
|
import { applyInitialTuiCliState, formatFatalStartupErrorForLog, formatFatalStartupErrorForUser, getInteractiveTerminalLaunchError } from './cli/tui-startup.ts';
|
|
55
56
|
import { wireSpokenTurnRuntime } from './audio/spoken-turn-wiring.ts';
|
|
@@ -62,8 +63,7 @@ import { createAutonomySurfacing, buildCalendarEventsLister, buildSkillDraftProp
|
|
|
62
63
|
import { startHardwareProbe } from './core/hardware-profile.ts';
|
|
63
64
|
import { readApprovalPostureFromConfig } from './permissions/approval-posture.ts';
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u', KEYBOARD_EXT_DISABLE = '\x1b[>4;0m' + '\x1b[?1l', PASTE_ENABLE = '\x1b[?2004h', PASTE_DISABLE = '\x1b[?2004l';
|
|
66
|
+
// Escape bytes and enter/exit sequencing live in renderer/terminal-escapes.ts (re-exported from @pellux/goodvibes-terminal-shell) so this file never holds its own drifting copy.
|
|
67
67
|
|
|
68
68
|
async function main() {
|
|
69
69
|
const stdout = process.stdout;
|
|
@@ -223,7 +223,9 @@ async function main() {
|
|
|
223
223
|
const unsubs: Array<() => void> = [];
|
|
224
224
|
let recoveryInterval: ReturnType<typeof setInterval> | null = null;
|
|
225
225
|
let stopSpokenOutputForExit: (() => Promise<void>) | null = null;
|
|
226
|
-
let recoveryPending = false;
|
|
226
|
+
let recoveryPending = false, pendingWorkspaceRegistration: PendingWorkspaceRegistrationState | null = null;
|
|
227
|
+
// Set by exitApp before the terminal-restore write; render() checks this so no late frame can paint over the screen after the terminal has been handed back.
|
|
228
|
+
let terminalRestored = false;
|
|
227
229
|
|
|
228
230
|
const sigintHandler = (): void => input.feed('\x03');
|
|
229
231
|
const unhandledRejectionHandler = createUnhandledRejectionHandler({
|
|
@@ -242,6 +244,8 @@ async function main() {
|
|
|
242
244
|
// spoken-audio drain below must not re-run teardown.
|
|
243
245
|
if (exiting) return;
|
|
244
246
|
exiting = true;
|
|
247
|
+
// Gate render() before anything else so no late frame follows the terminal-restore write below.
|
|
248
|
+
terminalRestored = true;
|
|
245
249
|
// Exit lets the spoken audio the user is already hearing finish inside a
|
|
246
250
|
// short bounded window (capped inside stopForExit) instead of killing the
|
|
247
251
|
// player mid-drain; queued-but-unplayed speech is dropped. Deliberate
|
|
@@ -263,8 +267,7 @@ async function main() {
|
|
|
263
267
|
stdout.removeListener('resize', resizeHandler);
|
|
264
268
|
process.removeListener('SIGINT', sigintHandler);
|
|
265
269
|
process.removeListener('unhandledRejection', unhandledRejectionHandler);
|
|
266
|
-
|
|
267
|
-
allowTerminalWrite(() => stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + FOCUS_DISABLE + CURSOR_SHOW + exitScreen));
|
|
270
|
+
allowTerminalWrite(() => stdout.write(buildExitSequence(cli.flags.noAltScreen)));
|
|
268
271
|
terminalOutputGuard.dispose();
|
|
269
272
|
stdin.setRawMode(false);
|
|
270
273
|
// The terminal is already restored above; only the process exit waits for
|
|
@@ -388,7 +391,7 @@ async function main() {
|
|
|
388
391
|
commandContext.scrollToLine = scrollToLine;
|
|
389
392
|
commandContext.clearScreen = () => {
|
|
390
393
|
compositor.resetDiff();
|
|
391
|
-
allowTerminalWrite(() => stdout.write(
|
|
394
|
+
allowTerminalWrite(() => stdout.write(CLEAR_VIEWPORT_HOME));
|
|
392
395
|
render();
|
|
393
396
|
};
|
|
394
397
|
commandContext.toggleActivitySidebar = () => {
|
|
@@ -472,6 +475,8 @@ async function main() {
|
|
|
472
475
|
};
|
|
473
476
|
|
|
474
477
|
const render = () => {
|
|
478
|
+
// The terminal has already been handed back to the shell; never paint another frame over it.
|
|
479
|
+
if (terminalRestored) return;
|
|
475
480
|
const { width, height } = getTerminalSize(stdout);
|
|
476
481
|
|
|
477
482
|
// Fire-and-forget refresh for the 'Coming up' sidebar section.
|
|
@@ -685,7 +690,7 @@ async function main() {
|
|
|
685
690
|
stdin.setRawMode(true);
|
|
686
691
|
stdin.resume();
|
|
687
692
|
stdin.setEncoding('utf8');
|
|
688
|
-
allowTerminalWrite(() => { markFocusModeEnabled(); return stdout.write((cli.flags.noAltScreen
|
|
693
|
+
allowTerminalWrite(() => { markFocusModeEnabled(); return stdout.write(buildEnterSequence(cli.flags.noAltScreen)); });
|
|
689
694
|
|
|
690
695
|
// Forced dark/light before first paint; auto (TTY) probes + repaints once if light.
|
|
691
696
|
const themeProbe = installStartupThemeProbe({
|
|
@@ -710,6 +715,7 @@ async function main() {
|
|
|
710
715
|
data,
|
|
711
716
|
pendingPermission,
|
|
712
717
|
recoveryPending,
|
|
718
|
+
pendingWorkspaceRegistration,
|
|
713
719
|
abortTurn: () => orchestrator.abort(),
|
|
714
720
|
conversation,
|
|
715
721
|
systemMessageRouter,
|
|
@@ -717,8 +723,7 @@ async function main() {
|
|
|
717
723
|
loadRecoveryConversation: () => loadRecoveryConversation({ homeDirectory }),
|
|
718
724
|
deleteRecoveryFile: () => deleteRecoveryFile({ homeDirectory }),
|
|
719
725
|
});
|
|
720
|
-
pendingPermission = blocking
|
|
721
|
-
recoveryPending = blocking.recoveryPending;
|
|
726
|
+
({ pendingPermission, recoveryPending, pendingWorkspaceRegistration } = blocking);
|
|
722
727
|
if (blocking.handled) {
|
|
723
728
|
return;
|
|
724
729
|
}
|
|
@@ -760,7 +765,7 @@ async function main() {
|
|
|
760
765
|
|
|
761
766
|
// Wire streaming-speed metrics, auto-save, and recovery — all run after the
|
|
762
767
|
// first render so they land as ambient context, never startup blockers.
|
|
763
|
-
({ recoveryInterval, recoveryPending } = wireSessionPersistenceAndRecovery({
|
|
768
|
+
({ recoveryInterval, recoveryPending, pendingWorkspaceRegistration } = wireSessionPersistenceAndRecovery({
|
|
764
769
|
buildCurrentSessionSnapshot,
|
|
765
770
|
runtime,
|
|
766
771
|
conversation,
|