@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,11 +1,22 @@
|
|
|
1
|
-
import type { MemoryRecord, MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { MemoryRecord, MemoryRegistry, MemoryVectorStats } from '@pellux/goodvibes-sdk/platform/state';
|
|
2
|
+
// W6-C2 (E6): the recall-honesty floor + eligibility receipt now live in the SDK as
|
|
3
|
+
// the ONE cross-surface contract (memory-recall-contract.ts) instead of being defined
|
|
4
|
+
// per surface. This file re-exports them unchanged so every existing agent consumer
|
|
5
|
+
// keeps importing from './memory-prompt.ts', while the SDK is the single source of the
|
|
6
|
+
// floor (60, the store's own baseline), the flagged-record exclusion, and the honest
|
|
7
|
+
// degraded-state distinction. The per-turn ranking below stays agent-local — it is
|
|
8
|
+
// injection wiring on top of the contract, not the contract itself.
|
|
9
|
+
import {
|
|
10
|
+
MIN_PROMPT_MEMORY_CONFIDENCE,
|
|
11
|
+
describeMemoryPromptEligibility,
|
|
12
|
+
isPromptActiveMemory,
|
|
13
|
+
} from '@pellux/goodvibes-sdk/platform/state';
|
|
14
|
+
import type { MemoryPromptEligibility } from '@pellux/goodvibes-sdk/platform/state';
|
|
4
15
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
export { MIN_PROMPT_MEMORY_CONFIDENCE, describeMemoryPromptEligibility, isPromptActiveMemory };
|
|
17
|
+
export type { MemoryPromptEligibility };
|
|
18
|
+
|
|
19
|
+
const DEFAULT_LIMIT = 10;
|
|
9
20
|
|
|
10
21
|
function sortMemoryForPrompt(left: MemoryRecord, right: MemoryRecord): number {
|
|
11
22
|
if (right.confidence !== left.confidence) return right.confidence - left.confidence;
|
|
@@ -20,11 +31,131 @@ function formatMemoryLine(record: MemoryRecord): string {
|
|
|
20
31
|
return `- [${record.scope}/${record.cls} ${record.confidence}%${tags}${provenance}] ${record.summary}`;
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Per-turn semantic ranking of an already-eligible memory set (Wave-4 W4-A1B).
|
|
36
|
+
*
|
|
37
|
+
* W4-A1 shipped eligibility (confidence + reviewState + provenance) as the hard trust
|
|
38
|
+
* gate but had no per-turn query to rank WITHIN that eligible set — records were only
|
|
39
|
+
* ever ordered by stored confidence/recency, regardless of whether they had anything to
|
|
40
|
+
* do with what the user actually just asked. `rankMemoryForTurn` never touches the gate
|
|
41
|
+
* itself: it only reorders the records that already cleared describeMemoryPromptEligibility,
|
|
42
|
+
* so a budget-limited cut (the top-N prompt slice) drops the least relevant record to
|
|
43
|
+
* THIS turn instead of an arbitrary one.
|
|
44
|
+
*
|
|
45
|
+
* Degrades honestly and says why whenever it can't score: no turn text supplied, the
|
|
46
|
+
* semantic index disabled/unavailable/empty, or a real query that the index has zero
|
|
47
|
+
* vector matches for. In every degraded case the eligible set still gets the prior
|
|
48
|
+
* confidence/recency order — never silently dropped, never silently reordered on data
|
|
49
|
+
* that isn't there.
|
|
50
|
+
*/
|
|
51
|
+
export interface MemoryTurnRankingResult {
|
|
52
|
+
/** The eligible records, ranked — by relevance when `scored` is true, else by the prior confidence/recency order. */
|
|
53
|
+
readonly records: readonly MemoryRecord[];
|
|
54
|
+
/** Relevance percent (0-100) per record id. Only populated when `scored` is true. */
|
|
55
|
+
readonly relevanceById: ReadonlyMap<string, number>;
|
|
56
|
+
/** True when semantic relevance to the current turn actually drove the ranking above. */
|
|
57
|
+
readonly scored: boolean;
|
|
58
|
+
/** Honest reason for the degrade when `scored` is false; null when scoring succeeded or there was nothing to rank. */
|
|
59
|
+
readonly degradedReason: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function describeTurnRelevanceIndexUnavailable(stats: MemoryVectorStats): string | null {
|
|
63
|
+
if (!stats.enabled) return 'the semantic memory index is disabled for this store';
|
|
64
|
+
if (!stats.available) return `the semantic memory index is unavailable${stats.error ? `: ${stats.error}` : ''}`;
|
|
65
|
+
if (stats.indexedRecords === 0) return 'the semantic memory index has no indexed records yet';
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Qualitative band for a 0-100 relevance-to-turn percent (F7a).
|
|
71
|
+
*
|
|
72
|
+
* A raw cosine-similarity percent like "28%" reads as misleadingly low out of
|
|
73
|
+
* context — it can still be the single best match among the eligible set, or
|
|
74
|
+
* a genuinely useful match on an absolute basis, but a bare number invites
|
|
75
|
+
* "only 28%? that's barely relevant." Choice made here: add a qualitative
|
|
76
|
+
* band NEXT TO the raw percent rather than normalizing against the turn's
|
|
77
|
+
* top score. Normalizing would make every record but the single best one
|
|
78
|
+
* read as comparatively weak even when several are strong matches in
|
|
79
|
+
* absolute terms, and would make a genuinely weak top match look like a
|
|
80
|
+
* confident 100%. A fixed absolute band keeps the number honest while
|
|
81
|
+
* making it readable at a glance.
|
|
82
|
+
*/
|
|
83
|
+
export function relevanceBand(percent: number): 'high match' | 'moderate match' | 'low match' {
|
|
84
|
+
if (percent >= 60) return 'high match';
|
|
85
|
+
if (percent >= 35) return 'moderate match';
|
|
86
|
+
return 'low match';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function rankMemoryForTurn(
|
|
90
|
+
memoryRegistry: MemoryRegistry,
|
|
91
|
+
eligible: readonly MemoryRecord[],
|
|
92
|
+
turnText: string | null | undefined,
|
|
93
|
+
): MemoryTurnRankingResult {
|
|
94
|
+
const fallbackOrder = (): readonly MemoryRecord[] => [...eligible].sort(sortMemoryForPrompt);
|
|
95
|
+
if (eligible.length === 0) {
|
|
96
|
+
return { records: [], relevanceById: new Map(), scored: false, degradedReason: null };
|
|
97
|
+
}
|
|
98
|
+
const trimmedTurnText = turnText?.trim() ?? '';
|
|
99
|
+
if (!trimmedTurnText) {
|
|
100
|
+
return {
|
|
101
|
+
records: fallbackOrder(),
|
|
102
|
+
relevanceById: new Map(),
|
|
103
|
+
scored: false,
|
|
104
|
+
degradedReason: 'no current-turn text available for this composition — using stored confidence/recency order',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const stats = memoryRegistry.vectorStats();
|
|
108
|
+
const indexUnavailable = describeTurnRelevanceIndexUnavailable(stats);
|
|
109
|
+
if (indexUnavailable) {
|
|
110
|
+
return {
|
|
111
|
+
records: fallbackOrder(),
|
|
112
|
+
relevanceById: new Map(),
|
|
113
|
+
scored: false,
|
|
114
|
+
degradedReason: `semantic index unavailable (${indexUnavailable}) — using stored confidence/recency order`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Request enough candidates back that every eligible record has a real chance to
|
|
118
|
+
// appear with its similarity score (searchSemantic slices its return to this limit
|
|
119
|
+
// after scoring, not before) — the SDK's vector store caps the underlying KNN search
|
|
120
|
+
// at 500 candidates regardless, so there is no point asking for more than that.
|
|
121
|
+
const requestLimit = Math.min(500, Math.max(eligible.length, 50));
|
|
122
|
+
const results = memoryRegistry.searchSemantic({ query: trimmedTurnText, limit: requestLimit });
|
|
123
|
+
const consultedSemanticIndex = results.some((entry) => entry.similarity > 0);
|
|
124
|
+
if (!consultedSemanticIndex) {
|
|
125
|
+
return {
|
|
126
|
+
records: fallbackOrder(),
|
|
127
|
+
relevanceById: new Map(),
|
|
128
|
+
scored: false,
|
|
129
|
+
degradedReason: "no semantic match for this turn's text — using stored confidence/recency order",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const eligibleIds = new Set(eligible.map((record) => record.id));
|
|
133
|
+
const relevanceById = new Map<string, number>();
|
|
134
|
+
for (const entry of results) {
|
|
135
|
+
if (!eligibleIds.has(entry.record.id)) continue;
|
|
136
|
+
relevanceById.set(entry.record.id, Math.round(Math.max(0, Math.min(1, entry.similarity)) * 100));
|
|
137
|
+
}
|
|
138
|
+
const ranked = [...eligible].sort((left, right) => {
|
|
139
|
+
const rightScore = relevanceById.get(right.id) ?? 0;
|
|
140
|
+
const leftScore = relevanceById.get(left.id) ?? 0;
|
|
141
|
+
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
142
|
+
return sortMemoryForPrompt(left, right);
|
|
143
|
+
});
|
|
144
|
+
return { records: ranked, relevanceById, scored: true, degradedReason: null };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface BuildReviewedMemoryPromptOptions {
|
|
148
|
+
readonly limit?: number;
|
|
149
|
+
/** The current turn's raw text (the seam this comes from: TURN_SUBMITTED's `prompt`).
|
|
150
|
+
* Used only to RANK the already-eligible set — never to admit an otherwise-ineligible record. */
|
|
151
|
+
readonly turnText?: string | null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function buildReviewedMemoryPrompt(memoryRegistry: MemoryRegistry, options: BuildReviewedMemoryPromptOptions = {}): string | null {
|
|
155
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
156
|
+
const eligible = memoryRegistry.getAll().filter(isPromptActiveMemory);
|
|
157
|
+
const ranking = rankMemoryForTurn(memoryRegistry, eligible, options.turnText);
|
|
158
|
+
const records = ranking.records.slice(0, Math.max(0, limit));
|
|
28
159
|
|
|
29
160
|
if (records.length === 0) return null;
|
|
30
161
|
return [
|
|
@@ -6,14 +6,14 @@ export type JsonRecord = Record<string, unknown>;
|
|
|
6
6
|
export type ApprovalActionId = 'approvals.approve' | 'approvals.deny' | 'approvals.cancel';
|
|
7
7
|
export type AutomationActionId =
|
|
8
8
|
| 'automation.jobs.run'
|
|
9
|
-
| 'automation.jobs.
|
|
10
|
-
| 'automation.jobs.
|
|
9
|
+
| 'automation.jobs.disable'
|
|
10
|
+
| 'automation.jobs.enable'
|
|
11
11
|
| 'automation.runs.cancel'
|
|
12
12
|
| 'automation.runs.retry'
|
|
13
|
-
| 'schedules.delete'
|
|
14
|
-
| 'schedules.disable'
|
|
15
|
-
| 'schedules.enable'
|
|
16
|
-
| 'schedules.run';
|
|
13
|
+
| 'automation.schedules.delete'
|
|
14
|
+
| 'automation.schedules.disable'
|
|
15
|
+
| 'automation.schedules.enable'
|
|
16
|
+
| 'automation.schedules.run';
|
|
17
17
|
export type OperatorActionId = ApprovalActionId | AutomationActionId;
|
|
18
18
|
export type OperatorActionHttpMethod = 'POST' | 'DELETE';
|
|
19
19
|
|
|
@@ -103,16 +103,20 @@ export const OPERATOR_ACTIONS: Record<OperatorActionId, OperatorActionDescriptor
|
|
|
103
103
|
pathTemplate: '/api/automation/jobs/{jobId}/run',
|
|
104
104
|
targetField: 'jobId',
|
|
105
105
|
},
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
// W6-C3: automation.jobs.pause/resume were retired (redundant with
|
|
107
|
+
// disable/enable — same {id,enabled} output, same semantics). The
|
|
108
|
+
// user-facing "pause"/"resume" verb is unchanged; only the wire action +
|
|
109
|
+
// path moved to the canonical disable/enable methods.
|
|
110
|
+
'automation.jobs.disable': {
|
|
111
|
+
action: 'automation.jobs.disable',
|
|
108
112
|
label: 'pause automation job',
|
|
109
|
-
pathTemplate: '/api/automation/jobs/{jobId}/
|
|
113
|
+
pathTemplate: '/api/automation/jobs/{jobId}/disable',
|
|
110
114
|
targetField: 'jobId',
|
|
111
115
|
},
|
|
112
|
-
'automation.jobs.
|
|
113
|
-
action: 'automation.jobs.
|
|
116
|
+
'automation.jobs.enable': {
|
|
117
|
+
action: 'automation.jobs.enable',
|
|
114
118
|
label: 'resume automation job',
|
|
115
|
-
pathTemplate: '/api/automation/jobs/{jobId}/
|
|
119
|
+
pathTemplate: '/api/automation/jobs/{jobId}/enable',
|
|
116
120
|
targetField: 'jobId',
|
|
117
121
|
},
|
|
118
122
|
'automation.runs.cancel': {
|
|
@@ -127,26 +131,26 @@ export const OPERATOR_ACTIONS: Record<OperatorActionId, OperatorActionDescriptor
|
|
|
127
131
|
pathTemplate: '/api/automation/runs/{runId}/retry',
|
|
128
132
|
targetField: 'runId',
|
|
129
133
|
},
|
|
130
|
-
'schedules.run': {
|
|
131
|
-
action: 'schedules.run',
|
|
134
|
+
'automation.schedules.run': {
|
|
135
|
+
action: 'automation.schedules.run',
|
|
132
136
|
label: 'run schedule',
|
|
133
137
|
pathTemplate: '/api/automation/schedules/{scheduleId}/run',
|
|
134
138
|
targetField: 'scheduleId',
|
|
135
139
|
},
|
|
136
|
-
'schedules.enable': {
|
|
137
|
-
action: 'schedules.enable',
|
|
140
|
+
'automation.schedules.enable': {
|
|
141
|
+
action: 'automation.schedules.enable',
|
|
138
142
|
label: 'enable schedule',
|
|
139
143
|
pathTemplate: '/api/automation/schedules/{scheduleId}/enable',
|
|
140
144
|
targetField: 'scheduleId',
|
|
141
145
|
},
|
|
142
|
-
'schedules.disable': {
|
|
143
|
-
action: 'schedules.disable',
|
|
146
|
+
'automation.schedules.disable': {
|
|
147
|
+
action: 'automation.schedules.disable',
|
|
144
148
|
label: 'disable schedule',
|
|
145
149
|
pathTemplate: '/api/automation/schedules/{scheduleId}/disable',
|
|
146
150
|
targetField: 'scheduleId',
|
|
147
151
|
},
|
|
148
|
-
'schedules.delete': {
|
|
149
|
-
action: 'schedules.delete',
|
|
152
|
+
'automation.schedules.delete': {
|
|
153
|
+
action: 'automation.schedules.delete',
|
|
150
154
|
label: 'delete schedule',
|
|
151
155
|
pathTemplate: '/api/automation/schedules/{scheduleId}',
|
|
152
156
|
targetField: 'scheduleId',
|
|
@@ -340,14 +344,14 @@ function scheduleIdFromActionPath(path: string): string {
|
|
|
340
344
|
}
|
|
341
345
|
|
|
342
346
|
export function formatOperatorActionSuccess(baseUrl: string, result: OperatorActionSuccess): string {
|
|
343
|
-
const scheduleId = result.methodId.startsWith('schedules.') ? scheduleIdFromActionPath(result.path) : '';
|
|
347
|
+
const scheduleId = result.methodId.startsWith('automation.schedules.') ? scheduleIdFromActionPath(result.path) : '';
|
|
344
348
|
return [
|
|
345
349
|
'Agent operator action completed',
|
|
346
350
|
` method: ${result.methodId}`,
|
|
347
351
|
` route: ${result.httpMethod} ${result.path}`,
|
|
348
352
|
` connected host: ${baseUrl}`,
|
|
349
353
|
` status: ${statusFromOperatorActionBody(result.body)}`,
|
|
350
|
-
...(scheduleId ? scheduleNextRouteLines(scheduleId, { deleted: result.methodId === 'schedules.delete' }) : []),
|
|
354
|
+
...(scheduleId ? scheduleNextRouteLines(scheduleId, { deleted: result.methodId === 'automation.schedules.delete' }) : []),
|
|
351
355
|
].join('\n');
|
|
352
356
|
}
|
|
353
357
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
-
import type {
|
|
4
|
+
import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
5
5
|
import { getTierForContextWindow, getTierPromptSupplement } from '@pellux/goodvibes-sdk/platform/providers';
|
|
6
6
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
7
|
-
import { buildReviewedMemoryPrompt, isPromptActiveMemory,
|
|
7
|
+
import { buildReviewedMemoryPrompt, describeMemoryPromptEligibility, isPromptActiveMemory, rankMemoryForTurn, relevanceBand } from './memory-prompt.ts';
|
|
8
8
|
import { AgentPersonaRegistry, buildActivePersonaPrompt } from './persona-registry.ts';
|
|
9
9
|
import { buildProjectContextPrompt, discoverProjectContextFiles } from './project-context-files.ts';
|
|
10
10
|
import { AgentRoutineRegistry, buildEnabledRoutinesPrompt, evaluateAgentRoutineReadiness } from './routine-registry.ts';
|
|
@@ -73,6 +73,10 @@ export interface RuntimePromptCompositionInput {
|
|
|
73
73
|
readonly operatorPolicy: string;
|
|
74
74
|
readonly shellPaths: ShellPathService;
|
|
75
75
|
readonly memoryRegistry: MemoryRegistry;
|
|
76
|
+
/** The active turn's raw text (TURN_SUBMITTED's `prompt`), used only to rank the
|
|
77
|
+
* already-eligible memory set by relevance to this turn — see rankMemoryForTurn.
|
|
78
|
+
* Null/undefined when there is no active turn (e.g. a follow-up composition). */
|
|
79
|
+
readonly turnText?: string | null;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
const RECEIPT_STORE_LIMIT = 200;
|
|
@@ -117,11 +121,6 @@ function statusFor(active: number, suppressed: number): PromptContextReceiptStat
|
|
|
117
121
|
return 'empty';
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
function memorySort(left: MemoryRecord, right: MemoryRecord): number {
|
|
121
|
-
if (right.confidence !== left.confidence) return right.confidence - left.confidence;
|
|
122
|
-
return right.updatedAt - left.updatedAt;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
124
|
function receiptSegment(input: Omit<PromptContextReceiptSegment, 'approxTokens'> & { readonly promptText?: string }): PromptContextReceiptSegment {
|
|
126
125
|
const promptChars = input.promptText?.length ?? input.promptChars;
|
|
127
126
|
return {
|
|
@@ -145,10 +144,12 @@ function buildRuntimePromptReceiptSegments(input: RuntimePromptCompositionInput)
|
|
|
145
144
|
const projectContext = discoverProjectContextFiles(input.shellPaths);
|
|
146
145
|
const projectContextPrompt = buildProjectContextPrompt(input.shellPaths) ?? '';
|
|
147
146
|
const memoryRecords = input.memoryRegistry.getAll();
|
|
148
|
-
const
|
|
147
|
+
const eligibleMemory = memoryRecords.filter(isPromptActiveMemory);
|
|
148
|
+
const memoryRanking = rankMemoryForTurn(input.memoryRegistry, eligibleMemory, input.turnText);
|
|
149
|
+
const activeMemory = memoryRanking.records.slice(0, 10);
|
|
149
150
|
const activeMemoryIds = new Set(activeMemory.map((record) => record.id));
|
|
150
151
|
const suppressedMemory = memoryRecords.filter((record) => !activeMemoryIds.has(record.id));
|
|
151
|
-
const memoryPrompt = buildReviewedMemoryPrompt(input.memoryRegistry) ?? '';
|
|
152
|
+
const memoryPrompt = buildReviewedMemoryPrompt(input.memoryRegistry, { turnText: input.turnText }) ?? '';
|
|
152
153
|
const routineSnapshot = AgentRoutineRegistry.fromShellPaths(input.shellPaths).snapshot();
|
|
153
154
|
const activeRoutines = routineSnapshot.enabledRoutines.filter((routine) => routine.reviewState === 'reviewed' && evaluateAgentRoutineReadiness(routine).ready);
|
|
154
155
|
const suppressedRoutines = routineSnapshot.enabledRoutines.filter((routine) => !activeRoutines.some((active) => active.id === routine.id));
|
|
@@ -231,17 +232,43 @@ function buildRuntimePromptReceiptSegments(input: RuntimePromptCompositionInput)
|
|
|
231
232
|
suppressedCount: suppressedMemory.length,
|
|
232
233
|
promptChars: memoryPrompt.length,
|
|
233
234
|
promptText: memoryPrompt,
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
// Honest degrade note (Wave-4 W4-A1B): when per-turn relevance scoring did not
|
|
236
|
+
// run — no active-turn text, semantic index unavailable, or no vector match —
|
|
237
|
+
// say so instead of silently presenting the fallback confidence/recency order as
|
|
238
|
+
// if it were a relevance ranking.
|
|
239
|
+
note: memoryRanking.scored ? undefined : (memoryRanking.degradedReason ?? undefined),
|
|
240
|
+
selected: activeMemory.map((record) => ({
|
|
236
241
|
id: record.id,
|
|
237
|
-
|
|
242
|
+
scope: record.scope,
|
|
243
|
+
class: record.cls,
|
|
238
244
|
confidence: record.confidence,
|
|
239
|
-
reason: record.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
245
|
+
reason: describeMemoryPromptEligibility(record).reason,
|
|
246
|
+
// Per-turn relevance (W4-A1B): honest wording, only present when actually scored.
|
|
247
|
+
// F7a: the raw percent is paired with a qualitative band (see relevanceBand)
|
|
248
|
+
// so a genuinely-lower-but-real score like "28%" doesn't read as noise.
|
|
249
|
+
...(memoryRanking.scored ? { relevance: `relevance to this turn: ${memoryRanking.relevanceById.get(record.id) ?? 0}% (${relevanceBand(memoryRanking.relevanceById.get(record.id) ?? 0)})` } : {}),
|
|
244
250
|
})),
|
|
251
|
+
suppressed: suppressedMemory.slice(0, 12).map((record) => {
|
|
252
|
+
// Honest, per-record reason (confidence + reviewState + provenance) — never a
|
|
253
|
+
// blanket "not reviewed"/"outside prompt limit" guess. A record lands in
|
|
254
|
+
// "suppressed" either because it genuinely failed eligibility, or because it
|
|
255
|
+
// passed eligibility but the top-10 prompt slice cut it off — those are
|
|
256
|
+
// different situations and must read differently. When the slice was
|
|
257
|
+
// relevance-ranked, say what this record's relevance to the turn was too, so a
|
|
258
|
+
// budget-limited cut reads as "less relevant than the other ten", not arbitrary.
|
|
259
|
+
const eligibility = describeMemoryPromptEligibility(record);
|
|
260
|
+
const relevanceSuffix = memoryRanking.scored
|
|
261
|
+
? ` (relevance to this turn: ${memoryRanking.relevanceById.get(record.id) ?? 0}% — ${relevanceBand(memoryRanking.relevanceById.get(record.id) ?? 0)})`
|
|
262
|
+
: '';
|
|
263
|
+
return {
|
|
264
|
+
id: record.id,
|
|
265
|
+
reviewState: record.reviewState,
|
|
266
|
+
confidence: record.confidence,
|
|
267
|
+
reason: eligibility.eligible
|
|
268
|
+
? `eligible (${eligibility.reason}) but outside the top-10 prompt slice${relevanceSuffix} — budget-limited, not a trust problem`
|
|
269
|
+
: eligibility.reason,
|
|
270
|
+
};
|
|
271
|
+
}),
|
|
245
272
|
}),
|
|
246
273
|
receiptSegment({
|
|
247
274
|
id: 'routines',
|
|
@@ -317,7 +344,7 @@ export function composeRuntimePromptWithReceipt(input: RuntimePromptCompositionI
|
|
|
317
344
|
buildVibePrompt(input.shellPaths),
|
|
318
345
|
buildProjectContextPrompt(input.shellPaths),
|
|
319
346
|
input.operatorPolicy,
|
|
320
|
-
buildReviewedMemoryPrompt(input.memoryRegistry),
|
|
347
|
+
buildReviewedMemoryPrompt(input.memoryRegistry, { turnText: input.turnText }),
|
|
321
348
|
buildEnabledRoutinesPrompt(input.shellPaths),
|
|
322
349
|
buildEnabledSkillsPrompt(input.shellPaths),
|
|
323
350
|
buildActivePersonaPrompt(input.shellPaths),
|
|
@@ -89,7 +89,7 @@ export function formatReminderScheduleFailure(failure: ReminderScheduleFailure):
|
|
|
89
89
|
? ' next make the connected GoodVibes host available outside Agent, then retry.'
|
|
90
90
|
: null,
|
|
91
91
|
failure.kind === 'connected_host_incompatible' || failure.kind === 'connected_host_route_unavailable'
|
|
92
|
-
? ' next update the connected GoodVibes host so public schedules.create is available.'
|
|
92
|
+
? ' next update the connected GoodVibes host so public automation.schedules.create is available.'
|
|
93
93
|
: null,
|
|
94
94
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
95
95
|
}
|
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
type RoutineScheduleSpec,
|
|
16
16
|
} from './routine-schedule-promotion.ts';
|
|
17
17
|
|
|
18
|
-
type ScheduleCreateInput = OperatorMethodInput<'schedules.create'>;
|
|
19
|
-
type ScheduleCreateOutput = OperatorMethodOutput<'schedules.create'>;
|
|
18
|
+
type ScheduleCreateInput = OperatorMethodInput<'automation.schedules.create'>;
|
|
19
|
+
type ScheduleCreateOutput = OperatorMethodOutput<'automation.schedules.create'>;
|
|
20
20
|
type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
|
|
21
21
|
type ScheduleDeliveryTargetInput = ScheduleDeliveryInput['targets'] extends readonly (infer T)[] ? T : never;
|
|
22
22
|
|
|
@@ -441,7 +441,7 @@ async function classifyReminderScheduleError(
|
|
|
441
441
|
return {
|
|
442
442
|
ok: false,
|
|
443
443
|
kind: 'connected_host_incompatible',
|
|
444
|
-
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule requirements; schedules.create is unavailable.',
|
|
444
|
+
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule requirements; automation.schedules.create is unavailable.',
|
|
445
445
|
route: REMINDER_SCHEDULE_ROUTE,
|
|
446
446
|
baseUrl: connection.baseUrl,
|
|
447
447
|
};
|
|
@@ -166,7 +166,7 @@ export function formatRoutineScheduleCorrelation(result: RoutineScheduleCorrelat
|
|
|
166
166
|
? ' next make the connected GoodVibes host available outside Agent, then retry.'
|
|
167
167
|
: null,
|
|
168
168
|
result.kind === 'connected_host_incompatible' || result.kind === 'connected_host_route_unavailable'
|
|
169
|
-
? ' next update the connected GoodVibes host so public schedules.list is available.'
|
|
169
|
+
? ' next update the connected GoodVibes host so public automation.schedules.list is available.'
|
|
170
170
|
: null,
|
|
171
171
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
172
172
|
}
|
|
@@ -218,7 +218,7 @@ export function formatRoutineScheduleFailure(failure: RoutineSchedulePromotionFa
|
|
|
218
218
|
? ' next make the connected GoodVibes host available outside Agent, then retry.'
|
|
219
219
|
: null,
|
|
220
220
|
failure.kind === 'connected_host_incompatible' || failure.kind === 'connected_host_route_unavailable'
|
|
221
|
-
? ' next update the connected GoodVibes host so public schedules.create is available.'
|
|
221
|
+
? ' next update the connected GoodVibes host so public automation.schedules.create is available.'
|
|
222
222
|
: null,
|
|
223
223
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
224
224
|
}
|
|
@@ -8,11 +8,11 @@ import { formatAgentRecordReviewState } from './record-labels.ts';
|
|
|
8
8
|
import type { AgentRoutineRecord } from './routine-registry.ts';
|
|
9
9
|
|
|
10
10
|
export const ROUTINE_SCHEDULE_ROUTE = '/api/automation/schedules';
|
|
11
|
-
export const ROUTINE_SCHEDULE_METHOD = 'schedules.create';
|
|
12
|
-
export const ROUTINE_SCHEDULE_LIST_METHOD = 'schedules.list';
|
|
11
|
+
export const ROUTINE_SCHEDULE_METHOD = 'automation.schedules.create';
|
|
12
|
+
export const ROUTINE_SCHEDULE_LIST_METHOD = 'automation.schedules.list';
|
|
13
13
|
|
|
14
|
-
type ScheduleCreateInput = OperatorMethodInput<'schedules.create'>;
|
|
15
|
-
type ScheduleCreateOutput = OperatorMethodOutput<'schedules.create'>;
|
|
14
|
+
type ScheduleCreateInput = OperatorMethodInput<'automation.schedules.create'>;
|
|
15
|
+
type ScheduleCreateOutput = OperatorMethodOutput<'automation.schedules.create'>;
|
|
16
16
|
type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
|
|
17
17
|
type ScheduleDeliveryTargetInput = ScheduleDeliveryInput['targets'] extends readonly (infer T)[] ? T : never;
|
|
18
18
|
|
|
@@ -388,7 +388,7 @@ async function classifyScheduleError(
|
|
|
388
388
|
return {
|
|
389
389
|
ok: false,
|
|
390
390
|
kind: 'connected_host_incompatible',
|
|
391
|
-
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule requirements; schedules.create is unavailable.',
|
|
391
|
+
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule requirements; automation.schedules.create is unavailable.',
|
|
392
392
|
route: ROUTINE_SCHEDULE_ROUTE,
|
|
393
393
|
baseUrl: connection.baseUrl,
|
|
394
394
|
};
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
type ScheduleCreateInput = RoutineSchedulePromotionPreview['payload'];
|
|
30
30
|
type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
|
|
31
|
-
type ScheduleListOutput = OperatorMethodOutput<'schedules.list'>;
|
|
31
|
+
type ScheduleListOutput = OperatorMethodOutput<'automation.schedules.list'>;
|
|
32
32
|
|
|
33
33
|
interface RoutineScheduleReceiptStoreFile {
|
|
34
34
|
readonly version: 1;
|
|
@@ -453,7 +453,7 @@ async function classifyScheduleListError(
|
|
|
453
453
|
return {
|
|
454
454
|
ok: false,
|
|
455
455
|
kind: 'connected_host_incompatible',
|
|
456
|
-
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule requirements; schedules.list is unavailable.',
|
|
456
|
+
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule requirements; automation.schedules.list is unavailable.',
|
|
457
457
|
route: ROUTINE_SCHEDULE_ROUTE,
|
|
458
458
|
baseUrl: connection.baseUrl,
|
|
459
459
|
};
|
|
@@ -123,7 +123,7 @@ export function formatScheduleEditFailure(failure: ScheduleEditFailure): string
|
|
|
123
123
|
? ' next pair or start the connected GoodVibes host so an operator token is available.'
|
|
124
124
|
: '',
|
|
125
125
|
failure.kind === 'connected_host_incompatible'
|
|
126
|
-
? ' next update the connected GoodVibes host so automation.jobs.
|
|
126
|
+
? ' next update the connected GoodVibes host so automation.jobs.update is available.'
|
|
127
127
|
: '',
|
|
128
128
|
].filter((line) => line !== '').join('\n');
|
|
129
129
|
}
|
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
type RoutineScheduleSpec,
|
|
22
22
|
} from './routine-schedule-promotion.ts';
|
|
23
23
|
|
|
24
|
-
type SchedulePatchInput = OperatorMethodInput<'automation.jobs.
|
|
25
|
-
type SchedulePatchOutput = OperatorMethodOutput<'automation.jobs.
|
|
26
|
-
type ScheduleListOutput = OperatorMethodOutput<'schedules.list'>;
|
|
24
|
+
type SchedulePatchInput = OperatorMethodInput<'automation.jobs.update'>;
|
|
25
|
+
type SchedulePatchOutput = OperatorMethodOutput<'automation.jobs.update'>;
|
|
26
|
+
type ScheduleListOutput = OperatorMethodOutput<'automation.schedules.list'>;
|
|
27
27
|
|
|
28
|
-
export const SCHEDULE_EDIT_METHOD = 'automation.jobs.
|
|
28
|
+
export const SCHEDULE_EDIT_METHOD = 'automation.jobs.update';
|
|
29
29
|
export const SCHEDULE_EDIT_ROUTE = '/api/automation/jobs/{jobId}';
|
|
30
30
|
|
|
31
31
|
export interface ParsedScheduleEditArgs {
|
|
@@ -442,7 +442,7 @@ async function classifyScheduleEditError(
|
|
|
442
442
|
return {
|
|
443
443
|
ok: false,
|
|
444
444
|
kind: 'connected_host_incompatible',
|
|
445
|
-
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule edit requirements; automation.jobs.
|
|
445
|
+
error: 'Connected GoodVibes host compatibility does not satisfy Agent schedule edit requirements; automation.jobs.update is unavailable.',
|
|
446
446
|
route: SCHEDULE_EDIT_ROUTE,
|
|
447
447
|
baseUrl: connection.baseUrl,
|
|
448
448
|
};
|