@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,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 AgentWorkspaceChannelCommandEditorKind = Extract<
|
|
4
6
|
AgentWorkspaceEditorKind,
|
|
@@ -9,51 +11,41 @@ export function isAgentWorkspaceChannelCommandEditorKind(kind: AgentWorkspaceEdi
|
|
|
9
11
|
return kind === 'channel-show' || kind === 'channel-doctor' || kind === 'channel-setup' || kind === 'channel-send';
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
title: 'Show Channel Setup',
|
|
48
|
-
selectedFieldIndex: 0,
|
|
49
|
-
message: 'Show read-only setup guidance for one channel. This does not pair, enable, or send through the channel.',
|
|
50
|
-
fields: [
|
|
51
|
-
{ id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
|
|
52
|
-
],
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
kind,
|
|
14
|
+
const CHANNEL_COMMAND_EDITOR_SPECS: Readonly<Record<AgentWorkspaceChannelCommandEditorKind, AgentWorkspaceEditorSpecEntry<AgentWorkspaceChannelCommandEditorKind>>> = {
|
|
15
|
+
'channel-send': {
|
|
16
|
+
mode: 'create',
|
|
17
|
+
title: 'Send Channel Message',
|
|
18
|
+
selectedFieldIndex: 0,
|
|
19
|
+
message: 'Send one message through a configured delivery target. Fill exactly one target field and type yes to confirm.',
|
|
20
|
+
fields: [
|
|
21
|
+
{ id: 'message', label: 'Message', value: '', required: true, multiline: true, hint: 'Plain-text message. Ctrl-J inserts a new line.' },
|
|
22
|
+
{ id: 'title', label: 'Title', value: '', required: false, multiline: false, hint: 'Optional delivery title. Blank uses the Agent default.' },
|
|
23
|
+
{ id: 'channel', label: 'Channel target', value: '', required: false, multiline: false, hint: 'Optional surface[:route[:label]], such as slack:ops:Ops or telephony:+15551234567.' },
|
|
24
|
+
{ id: 'route', label: 'Route target', value: '', required: false, multiline: false, hint: 'Optional route id or route:label.' },
|
|
25
|
+
{ id: 'webhook', label: 'Webhook URL', value: '', required: false, multiline: false, hint: 'Optional http(s) webhook target.' },
|
|
26
|
+
{ id: 'link', label: 'Link target', value: '', required: false, multiline: false, hint: 'Optional link delivery target.' },
|
|
27
|
+
{ id: 'confirm', label: 'Confirm', value: '', required: true, multiline: false, hint: 'Type yes to run /channels send with --yes.' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
'channel-doctor': {
|
|
31
|
+
mode: 'create',
|
|
32
|
+
title: 'Run Channel Doctor',
|
|
33
|
+
selectedFieldIndex: 0,
|
|
34
|
+
message: 'Inspect one connected channel route without sending messages or changing delivery state.',
|
|
35
|
+
fields: [
|
|
36
|
+
{ id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
'channel-setup': {
|
|
40
|
+
mode: 'create',
|
|
41
|
+
title: 'Show Channel Setup',
|
|
42
|
+
selectedFieldIndex: 0,
|
|
43
|
+
message: 'Show read-only setup guidance for one channel. This does not pair, enable, or send through the channel.',
|
|
44
|
+
fields: [
|
|
45
|
+
{ id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
'channel-show': {
|
|
57
49
|
mode: 'create',
|
|
58
50
|
title: 'Show Channel Detail',
|
|
59
51
|
selectedFieldIndex: 0,
|
|
@@ -61,5 +53,9 @@ export function createAgentWorkspaceChannelCommandEditor(kind: AgentWorkspaceCha
|
|
|
61
53
|
fields: [
|
|
62
54
|
{ id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
|
|
63
55
|
],
|
|
64
|
-
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function createAgentWorkspaceChannelCommandEditor(kind: AgentWorkspaceChannelCommandEditorKind): AgentWorkspaceLocalEditor {
|
|
60
|
+
return createAgentWorkspaceEditorFromTable(kind, CHANNEL_COMMAND_EDITOR_SPECS);
|
|
65
61
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Shared engine for the agent-workspace "command editor" domains (access, channel,
|
|
2
|
+
// knowledge, library, mcp, media, memory, operations, provider, session, skill-bundle,
|
|
3
|
+
// task). Every one of those domains follows the same two-stage shape: a declarative
|
|
4
|
+
// field-spec factory (kind -> { title, mode, message, fields }) and a submission step
|
|
5
|
+
// that reads the filled fields and builds a slash-command string handed to the
|
|
6
|
+
// shell-owned command router. This module factors that repeated structural pattern
|
|
7
|
+
// (the editor-construction factory, the submission dispatch/blocked-editor result
|
|
8
|
+
// shapes, and the handful of small helpers every domain re-implemented locally) into
|
|
9
|
+
// one place. Each domain keeps its own field content and command-building logic
|
|
10
|
+
// (real product value, not boilerplate) as entries in a `Record<Kind, ...>` table.
|
|
11
|
+
//
|
|
12
|
+
// W4-H2 (Wave 4): consolidates the ~26 near-identical editor+submission file pairs
|
|
13
|
+
// identified by the W4-H1 design ruling into this engine + per-domain data tables.
|
|
14
|
+
import type { AgentWorkspaceActionResult, AgentWorkspaceEditorField, AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
15
|
+
import { quoteSlashCommandArg } from './slash-command-parser.ts';
|
|
16
|
+
|
|
17
|
+
export type AgentWorkspaceFieldReader = (fieldId: string) => string;
|
|
18
|
+
|
|
19
|
+
/** The data shape every domain's per-kind editor literal already followed. */
|
|
20
|
+
export interface AgentWorkspaceEditorSpec {
|
|
21
|
+
readonly mode: 'create' | 'update' | 'delete';
|
|
22
|
+
readonly title: string;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
readonly selectedFieldIndex: number;
|
|
25
|
+
readonly fields: readonly AgentWorkspaceEditorField[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A table entry is either a static spec, or (for the handful of kinds whose wording
|
|
30
|
+
* depends on which kind was requested, such as voice-enable/voice-disable sharing one
|
|
31
|
+
* shape) a function of the kind that returns one.
|
|
32
|
+
*/
|
|
33
|
+
export type AgentWorkspaceEditorSpecEntry<K extends string> =
|
|
34
|
+
| AgentWorkspaceEditorSpec
|
|
35
|
+
| ((kind: K) => AgentWorkspaceEditorSpec);
|
|
36
|
+
|
|
37
|
+
export function createAgentWorkspaceEditorFromTable<K extends string>(
|
|
38
|
+
kind: K,
|
|
39
|
+
table: Readonly<Record<K, AgentWorkspaceEditorSpecEntry<K>>>,
|
|
40
|
+
): AgentWorkspaceLocalEditor {
|
|
41
|
+
const entry = table[kind];
|
|
42
|
+
const spec: AgentWorkspaceEditorSpec = typeof entry === 'function' ? (entry as (kind: K) => AgentWorkspaceEditorSpec)(kind) : (entry as AgentWorkspaceEditorSpec);
|
|
43
|
+
return {
|
|
44
|
+
kind: kind as AgentWorkspaceEditorKind,
|
|
45
|
+
mode: spec.mode,
|
|
46
|
+
title: spec.title,
|
|
47
|
+
selectedFieldIndex: spec.selectedFieldIndex,
|
|
48
|
+
message: spec.message,
|
|
49
|
+
fields: spec.fields,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type AgentWorkspaceCommandEditorSubmission =
|
|
54
|
+
| {
|
|
55
|
+
readonly kind: 'editor';
|
|
56
|
+
readonly editor: AgentWorkspaceLocalEditor;
|
|
57
|
+
readonly status: string;
|
|
58
|
+
readonly actionResult?: AgentWorkspaceActionResult;
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
readonly kind: 'dispatch';
|
|
62
|
+
readonly command: string;
|
|
63
|
+
readonly status: string;
|
|
64
|
+
readonly actionResult: AgentWorkspaceActionResult;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type AgentWorkspaceCommandDispatchSafety = NonNullable<AgentWorkspaceActionResult['safety']>;
|
|
68
|
+
|
|
69
|
+
export function isAffirmative(value: string): boolean {
|
|
70
|
+
return /^(y|yes|true)$/i.test(value.trim());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function splitCommaList(value: string): readonly string[] {
|
|
74
|
+
return value.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Pushes `flag, quoteSlashCommandArg(value)` onto `parts` when `value` is non-blank. */
|
|
78
|
+
export function appendOptionalArg(parts: string[], flag: string, value: string): void {
|
|
79
|
+
if (value.trim().length > 0) parts.push(flag, quoteSlashCommandArg(value));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The confirmed/dispatched result shape every domain built by hand: status is always
|
|
84
|
+
* `${title}.`, and the actionResult always carries kind: 'dispatched' with the same
|
|
85
|
+
* command, title, detail, and safety. Kinds whose actionResult.command legitimately
|
|
86
|
+
* differs from the dispatched command (for example a redacted secret) skip this
|
|
87
|
+
* helper and build the literal directly, same as before consolidation.
|
|
88
|
+
*/
|
|
89
|
+
export function dispatchCommandEditorSubmission(
|
|
90
|
+
command: string,
|
|
91
|
+
title: string,
|
|
92
|
+
detail: string,
|
|
93
|
+
safety: AgentWorkspaceCommandDispatchSafety,
|
|
94
|
+
): AgentWorkspaceCommandEditorSubmission {
|
|
95
|
+
return {
|
|
96
|
+
kind: 'dispatch',
|
|
97
|
+
command,
|
|
98
|
+
status: `${title}.`,
|
|
99
|
+
actionResult: { kind: 'dispatched', title, detail, command, safety },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The "stay in the editor" result shape used for unconfirmed actions and other
|
|
105
|
+
* validation failures: the editor's message is replaced with `message`, and status
|
|
106
|
+
* defaults to the same text (the convention most domains used). Pass an explicit
|
|
107
|
+
* `status` for the domains that show a shorter status line than the in-editor message.
|
|
108
|
+
*/
|
|
109
|
+
export function editorMessageSubmission(
|
|
110
|
+
editor: AgentWorkspaceLocalEditor,
|
|
111
|
+
message: string,
|
|
112
|
+
status: string = message,
|
|
113
|
+
): AgentWorkspaceCommandEditorSubmission {
|
|
114
|
+
return {
|
|
115
|
+
kind: 'editor',
|
|
116
|
+
editor: { ...editor, message },
|
|
117
|
+
status,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type AgentWorkspaceCommandSubmissionHandler = (
|
|
122
|
+
editor: AgentWorkspaceLocalEditor,
|
|
123
|
+
readField: AgentWorkspaceFieldReader,
|
|
124
|
+
) => AgentWorkspaceCommandEditorSubmission;
|
|
125
|
+
|
|
126
|
+
export function buildCommandEditorSubmissionFromTable<K extends string>(
|
|
127
|
+
kind: K,
|
|
128
|
+
editor: AgentWorkspaceLocalEditor,
|
|
129
|
+
readField: AgentWorkspaceFieldReader,
|
|
130
|
+
table: Readonly<Record<K, AgentWorkspaceCommandSubmissionHandler>>,
|
|
131
|
+
): AgentWorkspaceCommandEditorSubmission {
|
|
132
|
+
return table[kind](editor, readField);
|
|
133
|
+
}
|
|
@@ -10,6 +10,7 @@ import { buildAgentResearchReportPromptSubmission } from './agent-workspace-rese
|
|
|
10
10
|
import { buildAgentResearchRunPromptSubmission } from './agent-workspace-research-run-editor.ts';
|
|
11
11
|
import { buildAgentResearchSourcePromptSubmission } from './agent-workspace-research-source-editor.ts';
|
|
12
12
|
import { buildAgentReminderScheduleEditorSubmission } from './agent-workspace-reminder-schedule-editor.ts';
|
|
13
|
+
import { buildAgentWorkspaceCalendarConnectEditorSubmission } from './agent-workspace-calendar-connect-editor.ts';
|
|
13
14
|
import { buildAgentRoutineScheduleEditorSubmission } from './agent-workspace-routine-schedule-editor.ts';
|
|
14
15
|
import { buildAgentWorkspaceWebResearchSubmission } from './agent-workspace-web-research-editor.ts';
|
|
15
16
|
import type { AgentWorkspaceActionResult, AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
@@ -69,6 +70,7 @@ type AgentWorkspaceCommandEditorKind = AgentWorkspaceBasicCommandEditorKind | Ex
|
|
|
69
70
|
| 'secret-delete'
|
|
70
71
|
| 'routine-schedule'
|
|
71
72
|
| 'reminder-schedule'
|
|
73
|
+
| 'calendar-connect'
|
|
72
74
|
| 'knowledge-bookmarks'
|
|
73
75
|
| 'tts-prompt'
|
|
74
76
|
| 'image-input'
|
|
@@ -166,6 +168,7 @@ export function isAgentWorkspaceCommandEditorKind(kind: AgentWorkspaceEditorKind
|
|
|
166
168
|
|| kind === 'model-compare-analytics'
|
|
167
169
|
|| kind === 'routine-schedule'
|
|
168
170
|
|| kind === 'reminder-schedule'
|
|
171
|
+
|| kind === 'calendar-connect'
|
|
169
172
|
|| isAgentWorkspaceBasicCommandEditorKind(kind);
|
|
170
173
|
}
|
|
171
174
|
|
|
@@ -263,5 +266,6 @@ export function buildAgentWorkspaceCommandEditorSubmission(
|
|
|
263
266
|
return buildAgentWorkspaceBasicCommandEditorSubmission(editor, readField, commandDispatchAvailable);
|
|
264
267
|
}
|
|
265
268
|
if (editor.kind === 'reminder-schedule') return buildAgentReminderScheduleEditorSubmission(editor, readField, commandDispatchAvailable);
|
|
269
|
+
if (editor.kind === 'calendar-connect') return buildAgentWorkspaceCalendarConnectEditorSubmission(editor, readField, commandDispatchAvailable);
|
|
266
270
|
return buildAgentRoutineScheduleEditorSubmission(editor, readField, commandDispatchAvailable);
|
|
267
271
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor kinds that perform a direct host action on submit (OAuth calls,
|
|
3
|
+
* config/secrets writes) rather than building a slash command for the
|
|
4
|
+
* generic command-editor dispatch pipeline (agent-workspace-command-editor.ts).
|
|
5
|
+
* Centralizing the dispatch here keeps AgentWorkspace.submitLocalEditor()
|
|
6
|
+
* under the architecture line cap as new direct-action editor kinds are
|
|
7
|
+
* added (W4-A5 added email-connect-wizard alongside the existing
|
|
8
|
+
* subscription-login-start/finish/logout kinds).
|
|
9
|
+
*/
|
|
10
|
+
import type { CommandContext } from './command-registry.ts';
|
|
11
|
+
import type { AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
12
|
+
import {
|
|
13
|
+
submitAgentWorkspaceSubscriptionLoginFinishEditor,
|
|
14
|
+
submitAgentWorkspaceSubscriptionLoginStartEditor,
|
|
15
|
+
submitAgentWorkspaceSubscriptionLogoutEditor,
|
|
16
|
+
type AgentWorkspaceSubscriptionEditorHost,
|
|
17
|
+
} from './agent-workspace-subscription-editor.ts';
|
|
18
|
+
import { submitAgentWorkspaceEmailConnectWizardEditor, type AgentWorkspaceEmailConnectEditorHost } from './agent-workspace-email-connect-editor.ts';
|
|
19
|
+
import { submitAgentWorkspaceCalendarSubscribeWizardEditor, type AgentWorkspaceCalendarSubscribeEditorHost } from './agent-workspace-calendar-subscribe-editor.ts';
|
|
20
|
+
import { submitAgentWorkspaceCalendarOAuthEditor, type AgentWorkspaceCalendarOAuthEditorHost } from './agent-workspace-calendar-oauth-editor.ts';
|
|
21
|
+
|
|
22
|
+
type FieldReader = (id: string) => string;
|
|
23
|
+
type DirectEditorHost = AgentWorkspaceSubscriptionEditorHost & AgentWorkspaceEmailConnectEditorHost & AgentWorkspaceCalendarSubscribeEditorHost & AgentWorkspaceCalendarOAuthEditorHost;
|
|
24
|
+
|
|
25
|
+
/** Returns true when this editor kind was handled directly (caller should return without further dispatch). */
|
|
26
|
+
export function trySubmitDirectHostActionEditor(
|
|
27
|
+
host: DirectEditorHost,
|
|
28
|
+
editor: AgentWorkspaceLocalEditor,
|
|
29
|
+
context: CommandContext | null,
|
|
30
|
+
readField: FieldReader,
|
|
31
|
+
requestRender?: () => void,
|
|
32
|
+
): boolean {
|
|
33
|
+
if (editor.kind === 'subscription-login-start') {
|
|
34
|
+
void submitAgentWorkspaceSubscriptionLoginStartEditor(host, editor, context, readField).finally(() => requestRender?.());
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (editor.kind === 'subscription-login-finish') {
|
|
38
|
+
void submitAgentWorkspaceSubscriptionLoginFinishEditor(host, editor, context, readField).finally(() => requestRender?.());
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (editor.kind === 'subscription-logout') {
|
|
42
|
+
submitAgentWorkspaceSubscriptionLogoutEditor(host, editor, context, readField);
|
|
43
|
+
requestRender?.();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (editor.kind === 'email-connect-wizard') {
|
|
47
|
+
void submitAgentWorkspaceEmailConnectWizardEditor(host, editor, context, readField).finally(() => requestRender?.());
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (editor.kind === 'calendar-subscribe-wizard') {
|
|
51
|
+
void submitAgentWorkspaceCalendarSubscribeWizardEditor(host, editor, context, readField).finally(() => requestRender?.());
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (editor.kind === 'calendar-oauth-google' || editor.kind === 'calendar-oauth-outlook') {
|
|
55
|
+
void submitAgentWorkspaceCalendarOAuthEditor(host, editor, context, readField).finally(() => requestRender?.());
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email connect wizard (W4-A5) — promotes the "Inbox workflows" workspace
|
|
3
|
+
* card from a dead guidance card into a real, stepped connect flow.
|
|
4
|
+
*
|
|
5
|
+
* Security: the raw password NEVER travels through the generic slash-command
|
|
6
|
+
* dispatch pipeline (AgentWorkspace.dispatchWorkspaceCommand), because that
|
|
7
|
+
* pipeline echoes its `command` argument back into
|
|
8
|
+
* AgentWorkspace.lastActionResult.command for the 'inline' execution path
|
|
9
|
+
* (see handler.ts dispatchAgentWorkspaceCommand) — a raw command string
|
|
10
|
+
* containing the password would render it in the workspace UI. Instead this
|
|
11
|
+
* editor is a DIRECT host action (the same pattern as the subscription
|
|
12
|
+
* login editors in agent-workspace-subscription-editor.ts): it calls
|
|
13
|
+
* persistEmailConfigField() and EmailService.testConnection() directly
|
|
14
|
+
* against ctx.platform.configManager / ctx.platform.secretsManager and never
|
|
15
|
+
* builds a command string. Credential persistence reuses the exact same
|
|
16
|
+
* persistEmailConfigField() helper the /email set CLI command uses — one
|
|
17
|
+
* storage path, not a fork.
|
|
18
|
+
*/
|
|
19
|
+
import type { CommandContext } from './command-registry.ts';
|
|
20
|
+
import { isAffirmative } from './agent-workspace-editors.ts';
|
|
21
|
+
import { EmailService, ensureEmailConfigDefaults, type EmailConnectionTestResult } from '../agent/email/email-service.ts';
|
|
22
|
+
import { requireSecretsManager } from './commands/runtime-services.ts';
|
|
23
|
+
import { persistEmailConfigField } from './commands/email-runtime.ts';
|
|
24
|
+
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
25
|
+
import type { AgentWorkspaceActionResult, AgentWorkspaceEmailConnectStatus, AgentWorkspaceLocalEditor, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
|
|
26
|
+
|
|
27
|
+
type FieldReader = (id: string) => string;
|
|
28
|
+
|
|
29
|
+
export interface AgentWorkspaceEmailConnectEditorHost {
|
|
30
|
+
localEditor: AgentWorkspaceLocalEditor | null;
|
|
31
|
+
runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null;
|
|
32
|
+
status: string;
|
|
33
|
+
lastActionResult: AgentWorkspaceActionResult | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Fields, in wizard-step order. imapPort/smtpPort/smtpSecurity carry sane defaults. */
|
|
37
|
+
export function createEmailConnectWizardEditor(status: AgentWorkspaceEmailConnectStatus | null): AgentWorkspaceLocalEditor {
|
|
38
|
+
const message = !status
|
|
39
|
+
? 'Connect your inbox. Enter your IMAP/SMTP settings; the password is stored through the Agent secret manager, never in plain config.'
|
|
40
|
+
: status.connected
|
|
41
|
+
? `Currently connected as ${status.username} (${status.imapHost}). Re-enter settings below to reconnect or update credentials.`
|
|
42
|
+
: status.errors.length > 0
|
|
43
|
+
? 'Not connected — connect your inbox. Enter your IMAP/SMTP settings below.'
|
|
44
|
+
: 'Not connected — connect your inbox. Enter your IMAP/SMTP settings below.';
|
|
45
|
+
return {
|
|
46
|
+
kind: 'email-connect-wizard',
|
|
47
|
+
mode: 'create',
|
|
48
|
+
title: 'Connect Inbox',
|
|
49
|
+
selectedFieldIndex: 0,
|
|
50
|
+
message,
|
|
51
|
+
fields: [
|
|
52
|
+
{ id: 'imapHost', label: 'IMAP host', value: status?.imapHost ?? '', required: true, multiline: false, hint: 'For example imap.gmail.com.' },
|
|
53
|
+
{ id: 'imapPort', label: 'IMAP port', value: '993', required: true, multiline: false, hint: 'Default 993 (implicit TLS).' },
|
|
54
|
+
{ id: 'smtpHost', label: 'SMTP host', value: '', required: true, multiline: false, hint: 'For example smtp.gmail.com.' },
|
|
55
|
+
{ id: 'smtpPort', label: 'SMTP port', value: '587', required: true, multiline: false, hint: 'Default 587 (STARTTLS) or 465 (TLS).' },
|
|
56
|
+
{ id: 'smtpSecurity', label: 'SMTP security', value: 'auto', required: true, multiline: false, hint: 'tls, starttls, or auto (port-based).' },
|
|
57
|
+
{ id: 'username', label: 'Username', value: status?.username ?? '', required: true, multiline: false, hint: 'Mailbox login, usually your email address.' },
|
|
58
|
+
{ id: 'password', label: 'Password', value: '', required: true, multiline: false, hint: 'Stored through the Agent secret manager. Masked here and never rendered in results.', redact: true },
|
|
59
|
+
{ id: 'fromAddress', label: 'From address', value: status?.username ?? '', required: true, multiline: false, hint: 'Address used in the From: header when sending.' },
|
|
60
|
+
{ id: 'confirm', label: 'Confirm', value: '', required: true, multiline: false, hint: 'Type yes to store these settings and test the connection.' },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Field ids that persistEmailConfigField expects, in order — 'password' input maps to the 'passwordRef' config field. */
|
|
66
|
+
const FIELD_TO_EMAIL_KEY: Record<string, string> = {
|
|
67
|
+
imapHost: 'imapHost',
|
|
68
|
+
imapPort: 'imapPort',
|
|
69
|
+
smtpHost: 'smtpHost',
|
|
70
|
+
smtpPort: 'smtpPort',
|
|
71
|
+
smtpSecurity: 'smtpSecurity',
|
|
72
|
+
username: 'username',
|
|
73
|
+
fromAddress: 'fromAddress',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type EmailConnectionTester = (
|
|
77
|
+
getConfig: (key: string) => unknown,
|
|
78
|
+
secretsManager: { readonly get: (key: string) => Promise<string | null> },
|
|
79
|
+
) => { testConnection(): Promise<EmailConnectionTestResult> };
|
|
80
|
+
|
|
81
|
+
const defaultTester: EmailConnectionTester = (getConfig, secretsManager) => new EmailService({ getConfig, secretsManager });
|
|
82
|
+
|
|
83
|
+
export async function submitAgentWorkspaceEmailConnectWizardEditor(
|
|
84
|
+
host: AgentWorkspaceEmailConnectEditorHost,
|
|
85
|
+
editor: AgentWorkspaceLocalEditor,
|
|
86
|
+
context: CommandContext | null,
|
|
87
|
+
readField: FieldReader,
|
|
88
|
+
/** Injectable for tests — avoids real network I/O. Production callers omit this and get the real EmailService. */
|
|
89
|
+
buildTester: EmailConnectionTester = defaultTester,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
if (!isAffirmative(readField('confirm'))) {
|
|
92
|
+
host.localEditor = { ...editor, message: 'Inbox connection not confirmed. Type yes, then press Enter.' };
|
|
93
|
+
host.status = 'Inbox connection not confirmed.';
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!context) {
|
|
97
|
+
host.localEditor = { ...editor, message: 'This runtime has no command context; cannot connect the inbox.' };
|
|
98
|
+
host.status = 'Inbox connect unavailable.';
|
|
99
|
+
host.lastActionResult = { kind: 'error', title: 'Inbox connect unavailable', detail: 'No command context is available in this runtime.', safety: 'safe' };
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const secretsManager = requireSecretsManager(context);
|
|
105
|
+
ensureEmailConfigDefaults(context.platform.configManager);
|
|
106
|
+
const cm = context.platform.configManager as unknown as {
|
|
107
|
+
get: (key: string) => unknown;
|
|
108
|
+
setDynamic: (key: string, value: unknown) => void;
|
|
109
|
+
save: () => void;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const [fieldId, emailKey] of Object.entries(FIELD_TO_EMAIL_KEY)) {
|
|
113
|
+
const result = await persistEmailConfigField(cm, secretsManager, emailKey, readField(fieldId));
|
|
114
|
+
if (!result.ok) {
|
|
115
|
+
host.localEditor = { ...editor, message: result.error ?? `Could not save ${fieldId}.` };
|
|
116
|
+
host.status = result.error ?? 'Inbox connection settings were invalid.';
|
|
117
|
+
host.lastActionResult = { kind: 'error', title: 'Inbox settings invalid', detail: result.error ?? `Could not save ${fieldId}.`, safety: 'safe' };
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const passwordResult = await persistEmailConfigField(cm, secretsManager, 'passwordRef', readField('password'));
|
|
122
|
+
if (!passwordResult.ok) {
|
|
123
|
+
host.localEditor = { ...editor, message: passwordResult.error ?? 'Could not store the password.' };
|
|
124
|
+
host.status = passwordResult.error ?? 'Inbox password could not be stored.';
|
|
125
|
+
host.lastActionResult = { kind: 'error', title: 'Inbox password invalid', detail: passwordResult.error ?? 'Could not store the password.', safety: 'safe' };
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
cm.save();
|
|
129
|
+
|
|
130
|
+
const service = buildTester((key: string) => cm.get(key), secretsManager);
|
|
131
|
+
const test = await service.testConnection();
|
|
132
|
+
if (!test.ok) {
|
|
133
|
+
host.localEditor = null;
|
|
134
|
+
host.status = `Inbox connection failed (${test.stage ?? 'unknown'}).`;
|
|
135
|
+
host.lastActionResult = {
|
|
136
|
+
kind: 'error',
|
|
137
|
+
title: 'Inbox connection failed',
|
|
138
|
+
detail: `Could not verify the mailbox (${test.stage ?? 'unknown'} stage): ${test.error ?? 'unknown error'}. Settings were saved — fix the value above and reconnect.`,
|
|
139
|
+
safety: 'safe',
|
|
140
|
+
};
|
|
141
|
+
host.runtimeSnapshot = buildAgentWorkspaceRuntimeSnapshot(context);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
cm.setDynamic('email.enabled', true);
|
|
146
|
+
cm.save();
|
|
147
|
+
host.localEditor = null;
|
|
148
|
+
host.status = 'Inbox connected.';
|
|
149
|
+
host.lastActionResult = {
|
|
150
|
+
kind: 'refreshed',
|
|
151
|
+
title: 'Inbox connected',
|
|
152
|
+
detail: `IMAP and SMTP were verified for ${readField('username')}. Inbox workflows are ready.`,
|
|
153
|
+
safety: 'safe',
|
|
154
|
+
};
|
|
155
|
+
host.runtimeSnapshot = buildAgentWorkspaceRuntimeSnapshot(context);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
158
|
+
host.localEditor = { ...editor, message: detail };
|
|
159
|
+
host.status = detail;
|
|
160
|
+
host.lastActionResult = { kind: 'error', title: 'Inbox connect failed', detail, safety: 'safe' };
|
|
161
|
+
}
|
|
162
|
+
}
|