@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,61 +1,51 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
2
2
|
import type { AgentWorkspaceTaskCommandEditorKind } from './agent-workspace-task-command-editors.ts';
|
|
3
3
|
import { isAgentWorkspaceTaskCommandEditorKind } from './agent-workspace-task-command-editors.ts';
|
|
4
4
|
import { quoteSlashCommandArg } from './slash-command-parser.ts';
|
|
5
|
+
import type { AgentWorkspaceCommandEditorSubmission, AgentWorkspaceCommandSubmissionHandler, AgentWorkspaceFieldReader } from './agent-workspace-command-editor-engine.ts';
|
|
6
|
+
import { buildCommandEditorSubmissionFromTable, dispatchCommandEditorSubmission } from './agent-workspace-command-editor-engine.ts';
|
|
5
7
|
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
export type AgentWorkspaceTaskCommandEditorSubmission =
|
|
9
|
-
| {
|
|
10
|
-
readonly kind: 'editor';
|
|
11
|
-
readonly editor: AgentWorkspaceLocalEditor;
|
|
12
|
-
readonly status: string;
|
|
13
|
-
readonly actionResult?: AgentWorkspaceActionResult;
|
|
14
|
-
}
|
|
15
|
-
| {
|
|
16
|
-
readonly kind: 'dispatch';
|
|
17
|
-
readonly command: string;
|
|
18
|
-
readonly status: string;
|
|
19
|
-
readonly actionResult: AgentWorkspaceActionResult;
|
|
20
|
-
};
|
|
8
|
+
export type AgentWorkspaceTaskCommandEditorSubmission = AgentWorkspaceCommandEditorSubmission;
|
|
21
9
|
|
|
22
10
|
export function isAgentWorkspaceTaskCommandSubmissionKind(kind: AgentWorkspaceEditorKind): kind is AgentWorkspaceTaskCommandEditorKind {
|
|
23
11
|
return isAgentWorkspaceTaskCommandEditorKind(kind);
|
|
24
12
|
}
|
|
25
13
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
readField: AgentWorkspaceFieldReader,
|
|
29
|
-
): AgentWorkspaceTaskCommandEditorSubmission {
|
|
30
|
-
if (editor.kind === 'task-list-filter') {
|
|
14
|
+
const TASK_COMMAND_SUBMISSION_HANDLERS: Readonly<Record<AgentWorkspaceTaskCommandEditorKind, AgentWorkspaceCommandSubmissionHandler>> = {
|
|
15
|
+
'task-list-filter': (_editor, readField) => {
|
|
31
16
|
const filter = readField('filter');
|
|
32
17
|
const command = filter.length > 0 ? `/tasks list ${quoteSlashCommandArg(filter)}` : '/tasks list';
|
|
33
|
-
return
|
|
34
|
-
kind: 'dispatch',
|
|
18
|
+
return dispatchCommandEditorSubmission(
|
|
35
19
|
command,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
20
|
+
'Opening filtered task list',
|
|
21
|
+
'The workspace handed read-only connected-host task listing to the shell-owned command router.',
|
|
22
|
+
'read-only',
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
'task-output': (editor, readField) => taskInspection(editor, readField),
|
|
26
|
+
'task-show': (editor, readField) => taskInspection(editor, readField),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function taskInspection(editor: AgentWorkspaceLocalEditor, readField: AgentWorkspaceFieldReader): AgentWorkspaceCommandEditorSubmission {
|
|
46
30
|
const subcommand = editor.kind === 'task-output' ? 'output' : 'show';
|
|
47
31
|
const command = `/tasks ${subcommand} ${quoteSlashCommandArg(readField('taskId'))}`;
|
|
48
32
|
const title = editor.kind === 'task-output' ? 'Opening task output' : 'Opening task inspection';
|
|
49
|
-
return
|
|
50
|
-
kind: 'dispatch',
|
|
33
|
+
return dispatchCommandEditorSubmission(
|
|
51
34
|
command,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
35
|
+
title,
|
|
36
|
+
'The workspace handed read-only connected-host task inspection to the shell-owned command router.',
|
|
37
|
+
'read-only',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildAgentWorkspaceTaskCommandEditorSubmission(
|
|
42
|
+
editor: AgentWorkspaceLocalEditor,
|
|
43
|
+
readField: AgentWorkspaceFieldReader,
|
|
44
|
+
): AgentWorkspaceCommandEditorSubmission {
|
|
45
|
+
return buildCommandEditorSubmissionFromTable(
|
|
46
|
+
editor.kind as AgentWorkspaceTaskCommandEditorKind,
|
|
47
|
+
editor,
|
|
48
|
+
readField,
|
|
49
|
+
TASK_COMMAND_SUBMISSION_HANDLERS,
|
|
50
|
+
);
|
|
61
51
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
2
|
+
import type { AgentWorkspaceEditorSpecEntry } from './agent-workspace-command-editor-engine.ts';
|
|
3
|
+
import { createAgentWorkspaceEditorFromTable } from './agent-workspace-command-editor-engine.ts';
|
|
2
4
|
|
|
3
5
|
export type AgentWorkspaceTaskCommandEditorKind = Extract<
|
|
4
6
|
AgentWorkspaceEditorKind,
|
|
@@ -9,33 +11,26 @@ export function isAgentWorkspaceTaskCommandEditorKind(kind: AgentWorkspaceEditor
|
|
|
9
11
|
return kind === 'task-list-filter' || kind === 'task-show' || kind === 'task-output';
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
fields: [
|
|
33
|
-
{ id: 'taskId', label: 'Task id', value: '', required: true, multiline: false, hint: 'Connected-host task id from Host tasks.' },
|
|
34
|
-
],
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
kind,
|
|
14
|
+
const TASK_COMMAND_EDITOR_SPECS: Readonly<Record<AgentWorkspaceTaskCommandEditorKind, AgentWorkspaceEditorSpecEntry<AgentWorkspaceTaskCommandEditorKind>>> = {
|
|
15
|
+
'task-list-filter': {
|
|
16
|
+
mode: 'create',
|
|
17
|
+
title: 'Filter Host Tasks',
|
|
18
|
+
selectedFieldIndex: 0,
|
|
19
|
+
message: 'List connected-host tasks filtered by status or kind. This is read-only and never creates, retries, or cancels tasks.',
|
|
20
|
+
fields: [
|
|
21
|
+
{ id: 'filter', label: 'Status or kind', value: '', required: false, multiline: false, hint: 'Optional task status or kind. Blank lists recent tasks.' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
'task-output': {
|
|
25
|
+
mode: 'create',
|
|
26
|
+
title: 'Show Task Output',
|
|
27
|
+
selectedFieldIndex: 0,
|
|
28
|
+
message: 'Print one connected-host task output from the Agent workspace. This is read-only and does not retry, cancel, or mutate the task.',
|
|
29
|
+
fields: [
|
|
30
|
+
{ id: 'taskId', label: 'Task id', value: '', required: true, multiline: false, hint: 'Connected-host task id from Host tasks.' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
'task-show': {
|
|
39
34
|
mode: 'create',
|
|
40
35
|
title: 'Inspect Host Task',
|
|
41
36
|
selectedFieldIndex: 0,
|
|
@@ -43,5 +38,9 @@ export function createAgentWorkspaceTaskCommandEditor(kind: AgentWorkspaceTaskCo
|
|
|
43
38
|
fields: [
|
|
44
39
|
{ id: 'taskId', label: 'Task id', value: '', required: true, multiline: false, hint: 'Connected-host task id from Host tasks.' },
|
|
45
40
|
],
|
|
46
|
-
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function createAgentWorkspaceTaskCommandEditor(kind: AgentWorkspaceTaskCommandEditorKind): AgentWorkspaceLocalEditor {
|
|
45
|
+
return createAgentWorkspaceEditorFromTable(kind, TASK_COMMAND_EDITOR_SPECS);
|
|
47
46
|
}
|
|
@@ -241,7 +241,12 @@ export type AgentWorkspaceEditorKind =
|
|
|
241
241
|
| 'workplan-delete'
|
|
242
242
|
| 'workplan-clear-completed'
|
|
243
243
|
| 'routine-schedule'
|
|
244
|
-
| 'reminder-schedule'
|
|
244
|
+
| 'reminder-schedule'
|
|
245
|
+
| 'email-connect-wizard'
|
|
246
|
+
| 'calendar-connect'
|
|
247
|
+
| 'calendar-subscribe-wizard'
|
|
248
|
+
| 'calendar-oauth-google'
|
|
249
|
+
| 'calendar-oauth-outlook';
|
|
245
250
|
|
|
246
251
|
export type AgentWorkspaceLocalOperation =
|
|
247
252
|
| 'memory-edit'
|
|
@@ -365,6 +370,13 @@ export interface AgentWorkspaceLocalLibraryItem {
|
|
|
365
370
|
readonly missingRequirementCount?: number;
|
|
366
371
|
readonly missingRequirements?: readonly string[];
|
|
367
372
|
readonly startCount?: number;
|
|
373
|
+
/** Memory-only (Wave-4 W4-A1B): whether this record currently clears the prompt-injection
|
|
374
|
+
* recall floor, per describeMemoryPromptEligibility. */
|
|
375
|
+
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. */
|
|
379
|
+
readonly promptEligibilityReason?: string;
|
|
368
380
|
}
|
|
369
381
|
|
|
370
382
|
export interface AgentWorkspaceRecentReviewerHandoffArtifact {
|
|
@@ -628,6 +640,14 @@ export interface AgentWorkspaceCompanionAccessSummary {
|
|
|
628
640
|
readonly nextStep: string;
|
|
629
641
|
}
|
|
630
642
|
|
|
643
|
+
/** Honest, no-I/O email-connect status (config validation only) for the inbox connect card/wizard (W4-A5). */
|
|
644
|
+
export interface AgentWorkspaceEmailConnectStatus {
|
|
645
|
+
readonly connected: boolean;
|
|
646
|
+
readonly username: string;
|
|
647
|
+
readonly imapHost: string;
|
|
648
|
+
readonly errors: readonly string[];
|
|
649
|
+
}
|
|
650
|
+
|
|
631
651
|
export interface AgentWorkspaceRuntimeSnapshot {
|
|
632
652
|
readonly provider: string;
|
|
633
653
|
readonly model: string;
|
|
@@ -763,4 +783,18 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
763
783
|
readonly setupChecklist: readonly AgentWorkspaceSetupChecklistItem[];
|
|
764
784
|
readonly setupWizard: AgentSetupWizard;
|
|
765
785
|
readonly warnings: readonly string[];
|
|
786
|
+
/**
|
|
787
|
+
* True when the most recent render-path attempt to re-derive the live
|
|
788
|
+
* counters (memory count, routine counts + start counts — W4-A6) failed
|
|
789
|
+
* and the previous values below had to be kept. A fresh
|
|
790
|
+
* buildAgentWorkspaceRuntimeSnapshot() call is never stale (it just read
|
|
791
|
+
* everything); this only flips true from
|
|
792
|
+
* AgentWorkspace.syncLiveCountersForRender() when a live re-read errors.
|
|
793
|
+
* The render path uses this to label the counters honestly ("refreshing")
|
|
794
|
+
* instead of asserting a number the disk might already contradict.
|
|
795
|
+
*/
|
|
796
|
+
readonly liveCountersStale: boolean;
|
|
797
|
+
/** Honest email-connect status for the connect wizard's entry state (W4-A5). Null when unavailable in this runtime. */
|
|
798
|
+
readonly emailConnectStatus: AgentWorkspaceEmailConnectStatus | null;
|
|
799
|
+
readonly calendarOAuthConfigStatus: { readonly google: boolean; readonly microsoft: boolean } | null; // F1c: per-provider client-id-configured state for the advanced cards
|
|
766
800
|
}
|
|
@@ -21,7 +21,8 @@ import { appendAgentWorkspaceActionSearchText, backspaceAgentWorkspaceActionSear
|
|
|
21
21
|
import { applyAgentWorkspaceSetupCheckpointAction } from './agent-workspace-setup-checkpoint-action.ts';
|
|
22
22
|
import { agentWorkspaceSettingSchema, applyAgentWorkspaceSettingValue, buildAgentWorkspaceSettingActionDisplay, buildAgentWorkspaceSettingActionEffect, importAgentWorkspaceTuiSettings, isAgentWorkspaceActionVisible, type AgentWorkspaceSettingActionDisplay } from './agent-workspace-settings.ts';
|
|
23
23
|
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
24
|
-
import {
|
|
24
|
+
import { syncAgentWorkspaceLiveCounters } from './agent-workspace-live-counters.ts';
|
|
25
|
+
import { trySubmitDirectHostActionEditor } from './agent-workspace-direct-editor-submission.ts';
|
|
25
26
|
import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceActionSearchResult, AgentWorkspaceCategory, AgentWorkspaceCategoryGroup, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspacePromptDispatcher, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
|
|
26
27
|
import { ONBOARDING_COMPLETE_SYNTHETIC_ACTION, shouldShowOnboardingFinishFooter } from './agent-workspace-onboarding-finish.ts';
|
|
27
28
|
import { completeOnboardingAction, onSubscriptionLoginSuccessAction } from './agent-workspace-onboarding-actions.ts';
|
|
@@ -218,6 +219,8 @@ export class AgentWorkspace {
|
|
|
218
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.' };
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
/** W4-A6 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
|
+
|
|
221
224
|
cancelLocalEditor(): void {
|
|
222
225
|
if (!this.localEditor) return;
|
|
223
226
|
const title = this.localEditor.title;
|
|
@@ -554,19 +557,7 @@ export class AgentWorkspace {
|
|
|
554
557
|
this.status = `${missing.label} is required.`;
|
|
555
558
|
return;
|
|
556
559
|
}
|
|
557
|
-
if (editor.
|
|
558
|
-
void submitAgentWorkspaceSubscriptionLoginStartEditor(this, editor, this.context, (id) => this.editorField(id)).finally(() => requestRender?.());
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
if (editor.kind === 'subscription-login-finish') {
|
|
562
|
-
void submitAgentWorkspaceSubscriptionLoginFinishEditor(this, editor, this.context, (id) => this.editorField(id)).finally(() => requestRender?.());
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
if (editor.kind === 'subscription-logout') {
|
|
566
|
-
submitAgentWorkspaceSubscriptionLogoutEditor(this, editor, this.context, (id) => this.editorField(id));
|
|
567
|
-
requestRender?.();
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
560
|
+
if (trySubmitDirectHostActionEditor(this, editor, this.context, (id) => this.editorField(id), requestRender)) return;
|
|
570
561
|
if (isAgentWorkspaceCommandEditorKind(editor.kind)) {
|
|
571
562
|
this.submitCommandEditor(editor);
|
|
572
563
|
requestRender?.();
|
|
@@ -259,8 +259,30 @@ export class CommandRegistry {
|
|
|
259
259
|
private commands = new Map<string, SlashCommand>();
|
|
260
260
|
private aliasIndex = new Map<string, SlashCommand>();
|
|
261
261
|
|
|
262
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* Register a command. Also indexes all aliases for O(1) lookup.
|
|
264
|
+
*
|
|
265
|
+
* Collisions are LOUD: a second registration of the same primary name, or an
|
|
266
|
+
* alias that shadows an existing command's name or alias, throws instead of
|
|
267
|
+
* silently overwriting. Silent last-write-wins hid a real /session
|
|
268
|
+
* double-registration (session-workflow.ts vs commands/session.ts); the fork
|
|
269
|
+
* bypassed this check, so make it fail fast at startup instead.
|
|
270
|
+
*/
|
|
263
271
|
register(command: SlashCommand): void {
|
|
272
|
+
const existingByName = this.commands.get(command.name) ?? this.aliasIndex.get(command.name);
|
|
273
|
+
if (existingByName) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Command registration collision: "${command.name}" is already registered by /${existingByName.name}.`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
for (const alias of command.aliases ?? []) {
|
|
279
|
+
const holder = this.commands.get(alias) ?? this.aliasIndex.get(alias);
|
|
280
|
+
if (holder) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Command alias collision: "${alias}" on /${command.name} is already registered by /${holder.name}.`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
264
286
|
this.commands.set(command.name, command);
|
|
265
287
|
for (const alias of command.aliases ?? []) {
|
|
266
288
|
this.aliasIndex.set(alias, command);
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* calendar-connect-runtime.ts — the /calendar connect|disconnect|accounts subcommands
|
|
3
|
+
* and the provider-event merge for /calendar upcoming (W4-A10).
|
|
4
|
+
*
|
|
5
|
+
* The OAuth network dance lives here (not in the workspace card) because a command
|
|
6
|
+
* handler can print the authorization URL / device user-code IMMEDIATELY, then await
|
|
7
|
+
* the browser callback or poll the device code. The default path uses the bundled
|
|
8
|
+
* project client id + PKCE; a power user's own client id/secret come from config +
|
|
9
|
+
* the secret manager (captured by the advanced workspace card). No secret is ever put
|
|
10
|
+
* into a command string.
|
|
11
|
+
*
|
|
12
|
+
* The service is injectable so tests drive every path against fakes with no real
|
|
13
|
+
* network, port, or browser.
|
|
14
|
+
*/
|
|
15
|
+
import type { CalendarProviderId, DeviceCodeFlowStart, EventWindow, MergedCalendarEvent } from '@pellux/goodvibes-sdk/platform/calendar';
|
|
16
|
+
import type { CommandContext } from '../command-registry.ts';
|
|
17
|
+
import { requireSecretsManager } from './runtime-services.ts';
|
|
18
|
+
import { CalendarOAuthService, type CalendarConfigReader, type CalendarSecretSlice } from '../../agent/calendar/calendar-oauth-service.ts';
|
|
19
|
+
|
|
20
|
+
/** Factory seam: production builds the real service from context; tests inject a fake. */
|
|
21
|
+
export type CalendarServiceFactory = (ctx: CommandContext) => CalendarOAuthService;
|
|
22
|
+
|
|
23
|
+
const PROVIDER_LABEL: Record<CalendarProviderId, string> = { google: 'Google Calendar', microsoft: 'Microsoft Outlook' };
|
|
24
|
+
|
|
25
|
+
const CONNECTION_STATE_NOTE: Record<string, string> = {
|
|
26
|
+
connected: 'connected',
|
|
27
|
+
'refresh-due': 'connected (token refresh due)',
|
|
28
|
+
'reconnect-needed': 'reconnect needed — run the connect command again',
|
|
29
|
+
disconnected: 'disconnected',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Map the user's provider word to a provider id ('outlook' and 'microsoft' both ok). */
|
|
33
|
+
export function normalizeProvider(word: string | undefined): CalendarProviderId | null {
|
|
34
|
+
const w = (word ?? '').trim().toLowerCase();
|
|
35
|
+
if (w === 'google' || w === 'gcal') return 'google';
|
|
36
|
+
if (w === 'outlook' || w === 'microsoft' || w === 'ms' || w === 'msft') return 'microsoft';
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function defaultServiceFactory(ctx: CommandContext): CalendarOAuthService {
|
|
41
|
+
const config = ctx.platform.configManager as unknown as CalendarConfigReader;
|
|
42
|
+
const secrets = requireSecretsManager(ctx) as unknown as CalendarSecretSlice;
|
|
43
|
+
return new CalendarOAuthService({ config, secrets });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function deviceCodeMessage(label: string, start: DeviceCodeFlowStart): string {
|
|
47
|
+
return [
|
|
48
|
+
`Authorize ${label} on another device or browser:`,
|
|
49
|
+
` 1. Open ${start.verificationUri}`,
|
|
50
|
+
` 2. Enter the code: ${start.userCode}`,
|
|
51
|
+
start.verificationUriComplete ? ` (or open directly: ${start.verificationUriComplete})` : '',
|
|
52
|
+
' Waiting for approval...',
|
|
53
|
+
].filter(Boolean).join('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function authUrlMessage(label: string, url: string): string {
|
|
57
|
+
return [
|
|
58
|
+
`Authorize ${label} in your browser:`,
|
|
59
|
+
` ${url}`,
|
|
60
|
+
' (Attempting to open it automatically. Waiting for the redirect...)',
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The real guide for a bare `/calendar connect` (F1a): what connecting does, the
|
|
66
|
+
* two providers, and — since this build ships only the SDK's placeholder client
|
|
67
|
+
* ids until someone registers a project app — the honest situation and the exact
|
|
68
|
+
* next step, instead of a bare usage line that dead-ends with no explanation.
|
|
69
|
+
*/
|
|
70
|
+
function connectGuideMessage(): string {
|
|
71
|
+
return [
|
|
72
|
+
'Connect a calendar account over OAuth so its events show up alongside your local ones in /calendar upcoming.',
|
|
73
|
+
' Providers: google (Google Calendar), outlook (Microsoft Outlook)',
|
|
74
|
+
' /calendar connect google',
|
|
75
|
+
' /calendar connect outlook',
|
|
76
|
+
' (add --device to sign in with a device code instead of a browser)',
|
|
77
|
+
'',
|
|
78
|
+
'This build has no built-in Google/Microsoft sign-in configured yet — there is no project ' +
|
|
79
|
+
'client id shipped, so a bare connect will stop at the config step and tell you so. To ' +
|
|
80
|
+
'connect, you need a client id:',
|
|
81
|
+
' 1. Register a free OAuth app with the provider (Google Cloud Console, or the Azure portal for Microsoft).',
|
|
82
|
+
' 2. Copy the client id it gives you — a desktop/public-client app needs no secret.',
|
|
83
|
+
' 3. Open /agent personal-ops -> "Connect Google Calendar (advanced)" or "Connect Microsoft Outlook (advanced)" and paste it in.',
|
|
84
|
+
' 4. Run /calendar connect google (or outlook) again to authorize.',
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** An unrecognized provider word (not a missing one) keeps the short usage form. */
|
|
89
|
+
function unknownProviderMessage(word: string): string {
|
|
90
|
+
return `Unknown calendar provider '${word}'. Usage: /calendar connect <google|outlook> [--device]`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Best-effort browser open; never throws, never blocks the flow. */
|
|
94
|
+
function bestEffortOpenBrowser(url: string): void {
|
|
95
|
+
void (async () => {
|
|
96
|
+
try {
|
|
97
|
+
const { spawn } = await import('node:child_process');
|
|
98
|
+
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
99
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
100
|
+
const child = spawn(opener, args, { stdio: 'ignore', detached: true });
|
|
101
|
+
child.on('error', () => {});
|
|
102
|
+
child.unref();
|
|
103
|
+
} catch {
|
|
104
|
+
// ignore — the URL was already printed for the user to open manually
|
|
105
|
+
}
|
|
106
|
+
})();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function runCalendarConnect(
|
|
110
|
+
args: readonly string[],
|
|
111
|
+
ctx: CommandContext,
|
|
112
|
+
factory: CalendarServiceFactory = defaultServiceFactory,
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
const provider = normalizeProvider(args[1]);
|
|
115
|
+
if (!provider) {
|
|
116
|
+
ctx.print(args[1] ? unknownProviderMessage(args[1]) : connectGuideMessage());
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const label = PROVIDER_LABEL[provider];
|
|
120
|
+
const device = args.includes('--device');
|
|
121
|
+
const service = factory(ctx);
|
|
122
|
+
|
|
123
|
+
ctx.print(`Connecting ${label}...`);
|
|
124
|
+
const result = device
|
|
125
|
+
? await service.connectDeviceCode(provider, { onDeviceCode: (s) => ctx.print(deviceCodeMessage(label, s)) })
|
|
126
|
+
: await service.connectAuto(provider, {
|
|
127
|
+
openUrl: (url) => {
|
|
128
|
+
ctx.print(authUrlMessage(label, url));
|
|
129
|
+
bestEffortOpenBrowser(url);
|
|
130
|
+
},
|
|
131
|
+
onDeviceCode: (s) => ctx.print(deviceCodeMessage(label, s)),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!result.ok) {
|
|
135
|
+
ctx.print([
|
|
136
|
+
`Could not connect ${label} (${result.stage ?? 'unknown'} stage).`,
|
|
137
|
+
` ${result.error ?? 'unknown error'}`,
|
|
138
|
+
].join('\n'));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
ctx.print([
|
|
142
|
+
`Connected ${label}.`,
|
|
143
|
+
` account ${result.account?.label ?? provider}`,
|
|
144
|
+
` via ${result.transport === 'device-code' ? 'device code' : 'browser sign-in'}`,
|
|
145
|
+
` scopes ${(result.account?.scopes ?? []).join(', ') || '(none reported)'}`,
|
|
146
|
+
].join('\n'));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function runCalendarDisconnect(
|
|
150
|
+
args: readonly string[],
|
|
151
|
+
ctx: CommandContext,
|
|
152
|
+
factory: CalendarServiceFactory = defaultServiceFactory,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const provider = normalizeProvider(args[1]);
|
|
155
|
+
if (!provider) {
|
|
156
|
+
ctx.print('Usage: /calendar disconnect <google|outlook>');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const label = PROVIDER_LABEL[provider];
|
|
160
|
+
const service = factory(ctx);
|
|
161
|
+
const { revokedRemotely } = await service.disconnect(provider);
|
|
162
|
+
ctx.print([
|
|
163
|
+
`Disconnected ${label}.`,
|
|
164
|
+
revokedRemotely
|
|
165
|
+
? ' The access was revoked at the provider and local tokens were cleared.'
|
|
166
|
+
: ' Local tokens were cleared. This provider offers no token-revocation endpoint, so revoke access in your account settings if desired.',
|
|
167
|
+
].join('\n'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function runCalendarAccounts(
|
|
171
|
+
ctx: CommandContext,
|
|
172
|
+
factory: CalendarServiceFactory = defaultServiceFactory,
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
const service = factory(ctx);
|
|
175
|
+
const accounts = await service.listAccounts();
|
|
176
|
+
if (accounts.length === 0) {
|
|
177
|
+
ctx.print([
|
|
178
|
+
'Connected calendar accounts',
|
|
179
|
+
' None. Connect one with /calendar connect google or /calendar connect outlook',
|
|
180
|
+
].join('\n'));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
ctx.print([
|
|
184
|
+
`Connected calendar accounts (${accounts.length})`,
|
|
185
|
+
...accounts.map(({ account, state }) =>
|
|
186
|
+
` ${PROVIDER_LABEL[account.provider]} ${account.label} [${CONNECTION_STATE_NOTE[state] ?? state}]`,
|
|
187
|
+
),
|
|
188
|
+
].join('\n'));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Best-effort fetch of provider events for the upcoming window, source-labeled. A
|
|
193
|
+
* provider failure (reconnect needed, rate limit, etc.) is reported as an honest note
|
|
194
|
+
* rather than aborting the whole /calendar upcoming view.
|
|
195
|
+
*/
|
|
196
|
+
export async function fetchProviderUpcoming(
|
|
197
|
+
ctx: CommandContext,
|
|
198
|
+
window: EventWindow,
|
|
199
|
+
factory: CalendarServiceFactory = defaultServiceFactory,
|
|
200
|
+
): Promise<{ readonly events: MergedCalendarEvent[]; readonly notes: string[] }> {
|
|
201
|
+
const service = factory(ctx);
|
|
202
|
+
const accounts = await service.listAccounts();
|
|
203
|
+
if (accounts.length === 0) return { events: [], notes: [] };
|
|
204
|
+
const events: MergedCalendarEvent[] = [];
|
|
205
|
+
const notes: string[] = [];
|
|
206
|
+
for (const { account } of accounts) {
|
|
207
|
+
try {
|
|
208
|
+
const config = await service.resolveConfig(account.provider);
|
|
209
|
+
const providerEvents = await service.listEventsForProvider(config, window);
|
|
210
|
+
events.push(...providerEvents);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
notes.push(` ${PROVIDER_LABEL[account.provider]}: could not load events — ${error instanceof Error ? error.message : String(error)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { events, notes };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Render a source label for a merged event. */
|
|
219
|
+
export function sourceLabel(source: MergedCalendarEvent['source']): string {
|
|
220
|
+
switch (source) {
|
|
221
|
+
case 'google-api': return 'google';
|
|
222
|
+
case 'microsoft-graph': return 'outlook';
|
|
223
|
+
case 'ics-feed': return 'feed';
|
|
224
|
+
default: return 'local';
|
|
225
|
+
}
|
|
226
|
+
}
|