@pellux/goodvibes-agent 0.1.7 → 0.1.8
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 +25 -0
- package/README.md +3 -1
- package/docs/README.md +2 -2
- package/docs/deployment-and-services.md +1 -1
- package/docs/getting-started.md +5 -3
- package/package.json +1 -2
- package/src/agent/routine-registry.ts +389 -0
- package/src/cli/management-commands.ts +8 -12
- package/src/cli/package-verification.ts +1 -2
- package/src/input/agent-workspace.ts +18 -2
- package/src/input/commands/control-room-runtime.ts +7 -28
- package/src/input/commands/health-runtime.ts +4 -4
- package/src/input/commands/operator-runtime.ts +17 -45
- package/src/input/commands/remote-runtime.ts +7 -22
- package/src/input/commands/routines-runtime.ts +232 -0
- package/src/input/commands/session-content.ts +3 -16
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/session.ts +19 -26
- package/src/input/commands/tasks-runtime.ts +28 -102
- package/src/input/commands.ts +2 -0
- package/src/input/handler-picker-routes.ts +2 -3
- package/src/panels/provider-health-domains.ts +3 -3
- package/src/renderer/agent-workspace.ts +2 -1
- package/src/renderer/live-tail-modal.ts +7 -7
- package/src/renderer/process-indicator.ts +8 -8
- package/src/renderer/process-modal.ts +9 -9
- package/src/runtime/bootstrap.ts +2 -0
- package/src/runtime/services.ts +2 -20
- package/src/tools/wrfc-agent-guard.ts +37 -1
- package/src/version.ts +1 -1
- package/.goodvibes/agents/reviewer.md +0 -48
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
2
|
import type { RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
|
|
3
3
|
import { reviewWorktreeAttachments } from '@/runtime/index.ts';
|
|
4
|
-
import { requireOperatorClient,
|
|
5
|
-
|
|
4
|
+
import { requireOperatorClient, requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
5
|
+
|
|
6
|
+
const BLOCKED_TASK_MUTATIONS: ReadonlySet<string> = new Set([
|
|
7
|
+
'create',
|
|
8
|
+
'update',
|
|
9
|
+
'complete',
|
|
10
|
+
'fail',
|
|
11
|
+
'cancel',
|
|
12
|
+
'pause',
|
|
13
|
+
'resume',
|
|
14
|
+
'retry',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function printTaskMutationBlocked(print: (text: string) => void, subcommand: string): void {
|
|
18
|
+
print([
|
|
19
|
+
`Task mutation "${subcommand}" is blocked in GoodVibes Agent.`,
|
|
20
|
+
' policy: runtime tasks are read-only from the Agent surface; normal work stays in the main conversation.',
|
|
21
|
+
' durable tasks: use /workplan for visible planning and task tracking.',
|
|
22
|
+
' build/fix/review: use /delegate <task> to hand explicit implementation work to GoodVibes TUI.',
|
|
23
|
+
' result: no local runtime task state was changed.',
|
|
24
|
+
].join('\n'));
|
|
25
|
+
}
|
|
6
26
|
|
|
7
27
|
function sortRuntimeTasks(tasks: RuntimeTask[]): RuntimeTask[] {
|
|
8
28
|
const statusOrder: TaskLifecycleState[] = ['running', 'queued', 'blocked', 'failed', 'completed', 'cancelled'];
|
|
@@ -32,8 +52,8 @@ export function registerTasksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
32
52
|
registry.register({
|
|
33
53
|
name: 'tasks',
|
|
34
54
|
aliases: ['task'],
|
|
35
|
-
description: 'Inspect
|
|
36
|
-
usage: '[list [status|kind] | show <taskId> | output <taskId>
|
|
55
|
+
description: 'Inspect runtime tasks without starting or mutating local background work',
|
|
56
|
+
usage: '[list [status|kind] | show <taskId> | output <taskId>]',
|
|
37
57
|
handler(args, ctx) {
|
|
38
58
|
if (args.length === 0) {
|
|
39
59
|
if (ctx.showPanel) ctx.showPanel('tasks');
|
|
@@ -96,7 +116,7 @@ export function registerTasksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
96
116
|
return worktrees.total > 0
|
|
97
117
|
? [
|
|
98
118
|
` worktrees: ${worktrees.total} tracked (${worktrees.active} active / ${worktrees.paused} paused / ${worktrees.pendingCleanup} cleanup)`,
|
|
99
|
-
|
|
119
|
+
' worktree next: open GoodVibes TUI in the target workspace for recovery.',
|
|
100
120
|
]
|
|
101
121
|
: [];
|
|
102
122
|
})(),
|
|
@@ -125,106 +145,12 @@ export function registerTasksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
125
145
|
return;
|
|
126
146
|
}
|
|
127
147
|
|
|
128
|
-
if (subcommand
|
|
129
|
-
|
|
130
|
-
const kind = args[1];
|
|
131
|
-
const owner = args[2];
|
|
132
|
-
const title = args.slice(3).join(' ').trim();
|
|
133
|
-
if (!kind || !owner || !title) {
|
|
134
|
-
ctx.print('Usage: /tasks create <kind> <owner> <title...>');
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const validKinds = new Set(['exec', 'agent', 'acp', 'scheduler', 'daemon', 'mcp', 'plugin', 'integration']);
|
|
138
|
-
if (!validKinds.has(kind)) {
|
|
139
|
-
ctx.print(`Unknown task kind: ${kind}`);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const task = opsApi.tasks.create({
|
|
143
|
-
kind: kind as import('@/runtime/index.ts').TaskKind,
|
|
144
|
-
owner,
|
|
145
|
-
title,
|
|
146
|
-
description: title,
|
|
147
|
-
});
|
|
148
|
-
ctx.print(`Created task ${task.id} (${task.kind}) for ${task.owner}.`);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (subcommand === 'update') {
|
|
153
|
-
const opsApi = requireOpsApi(ctx);
|
|
154
|
-
const taskId = args[1];
|
|
155
|
-
const field = args[2];
|
|
156
|
-
const value = args.slice(3).join(' ').trim();
|
|
157
|
-
if (!taskId || !field || !value) {
|
|
158
|
-
ctx.print('Usage: /tasks update <taskId> <title|description|result> <value...>');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (field !== 'title' && field !== 'description' && field !== 'result') {
|
|
162
|
-
ctx.print(`Unsupported task update field: ${field}`);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
opsApi.tasks.update(taskId, field === 'result' ? { result: value } : { [field]: value });
|
|
166
|
-
ctx.print(`Updated task ${taskId} field ${field}.`);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (subcommand === 'complete') {
|
|
171
|
-
const opsApi = requireOpsApi(ctx);
|
|
172
|
-
const taskId = args[1];
|
|
173
|
-
if (!taskId) {
|
|
174
|
-
ctx.print('Usage: /tasks complete <taskId> [result]');
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const result = args.slice(2).join(' ').trim() || undefined;
|
|
178
|
-
opsApi.tasks.complete(taskId, result);
|
|
179
|
-
ctx.print(`Completed task ${taskId}.`);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (subcommand === 'fail') {
|
|
184
|
-
const opsApi = requireOpsApi(ctx);
|
|
185
|
-
const taskId = args[1];
|
|
186
|
-
const errorText = args.slice(2).join(' ').trim();
|
|
187
|
-
if (!taskId || !errorText) {
|
|
188
|
-
ctx.print('Usage: /tasks fail <taskId> <error...>');
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
opsApi.tasks.fail(taskId, { error: errorText });
|
|
192
|
-
ctx.print(`Failed task ${taskId}.`);
|
|
148
|
+
if (BLOCKED_TASK_MUTATIONS.has(subcommand)) {
|
|
149
|
+
printTaskMutationBlocked(ctx.print, subcommand);
|
|
193
150
|
return;
|
|
194
151
|
}
|
|
195
152
|
|
|
196
|
-
|
|
197
|
-
const note = args.slice(2).join(' ').trim() || undefined;
|
|
198
|
-
if (!taskId) {
|
|
199
|
-
ctx.print(`Usage: /tasks ${subcommand} <taskId> [note]`);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const opsApi = requireOpsApi(ctx);
|
|
203
|
-
try {
|
|
204
|
-
switch (subcommand) {
|
|
205
|
-
case 'cancel':
|
|
206
|
-
opsApi.tasks.cancel(taskId, note);
|
|
207
|
-
ctx.print(`Cancelled task ${taskId}.`);
|
|
208
|
-
return;
|
|
209
|
-
case 'pause':
|
|
210
|
-
opsApi.tasks.pause(taskId, note);
|
|
211
|
-
ctx.print(`Paused task ${taskId}.`);
|
|
212
|
-
return;
|
|
213
|
-
case 'resume':
|
|
214
|
-
opsApi.tasks.resume(taskId, note);
|
|
215
|
-
ctx.print(`Resumed task ${taskId}.`);
|
|
216
|
-
return;
|
|
217
|
-
case 'retry':
|
|
218
|
-
opsApi.tasks.retry(taskId, note);
|
|
219
|
-
ctx.print(`Re-queued task ${taskId}.`);
|
|
220
|
-
return;
|
|
221
|
-
default:
|
|
222
|
-
ctx.print(`Unknown tasks subcommand: ${subcommand}`);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
} catch (error) {
|
|
226
|
-
ctx.print(summarizeError(error));
|
|
227
|
-
}
|
|
153
|
+
ctx.print(`Unknown tasks subcommand: ${subcommand}`);
|
|
228
154
|
},
|
|
229
155
|
});
|
|
230
156
|
}
|
package/src/input/commands.ts
CHANGED
|
@@ -57,6 +57,7 @@ import { registerAgentExternalizedTuiCommands } from './commands/agent-externali
|
|
|
57
57
|
import { registerDelegationRuntimeCommands } from './commands/delegation-runtime.ts';
|
|
58
58
|
import { registerPersonasRuntimeCommands } from './commands/personas-runtime.ts';
|
|
59
59
|
import { registerAgentSkillsRuntimeCommands } from './commands/agent-skills-runtime.ts';
|
|
60
|
+
import { registerRoutinesRuntimeCommands } from './commands/routines-runtime.ts';
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -67,6 +68,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
67
68
|
registerAgentWorkspaceRuntimeCommands(registry);
|
|
68
69
|
registerPersonasRuntimeCommands(registry);
|
|
69
70
|
registerAgentSkillsRuntimeCommands(registry);
|
|
71
|
+
registerRoutinesRuntimeCommands(registry);
|
|
70
72
|
registerDelegationRuntimeCommands(registry);
|
|
71
73
|
registerConfigCommand(registry);
|
|
72
74
|
registerOperatorRuntimeCommands(registry);
|
|
@@ -256,7 +256,7 @@ type LiveTailRouteState = {
|
|
|
256
256
|
active: boolean;
|
|
257
257
|
scrollUp: () => void;
|
|
258
258
|
scrollDown: () => void;
|
|
259
|
-
killProcess: () =>
|
|
259
|
+
killProcess: () => boolean;
|
|
260
260
|
close: () => void;
|
|
261
261
|
};
|
|
262
262
|
processModal: {
|
|
@@ -270,8 +270,7 @@ export function handleLiveTailToken(state: LiveTailRouteState, token: InputToken
|
|
|
270
270
|
if (!state.liveTailModal.active) return false;
|
|
271
271
|
|
|
272
272
|
const killAndReturn = (): void => {
|
|
273
|
-
state.liveTailModal.killProcess();
|
|
274
|
-
state.handleEscape();
|
|
273
|
+
if (state.liveTailModal.killProcess()) state.handleEscape();
|
|
275
274
|
};
|
|
276
275
|
|
|
277
276
|
if (token.type === 'key') {
|
|
@@ -200,15 +200,15 @@ export function buildProviderHealthDomainSummaries(
|
|
|
200
200
|
summary: worktreeSummary.total === 0
|
|
201
201
|
? 'no persisted worktrees'
|
|
202
202
|
: `${worktreeSummary.total} tracked / ${worktreeIssues} need review`,
|
|
203
|
-
next:
|
|
203
|
+
next: 'externalized to GoodVibes TUI',
|
|
204
204
|
details: [
|
|
205
205
|
worktreeSummary.paused > 0 ? `${worktreeSummary.paused} paused worktree(s)` : '',
|
|
206
206
|
worktreeSummary.pendingCleanup > 0 ? `${worktreeSummary.pendingCleanup} cleanup pending` : '',
|
|
207
207
|
worktreeSummary.discard > 0 ? `${worktreeSummary.discard} marked discard` : '',
|
|
208
208
|
].filter(Boolean),
|
|
209
209
|
nextSteps: worktreeIssues > 0
|
|
210
|
-
? ['
|
|
211
|
-
: ['
|
|
210
|
+
? ['Open GoodVibes TUI in the target workspace for recovery', '/delegate <task> for explicit build/fix/review recovery']
|
|
211
|
+
: ['No Agent worktree action available; use GoodVibes TUI when repository recovery is needed'],
|
|
212
212
|
});
|
|
213
213
|
|
|
214
214
|
return summaries;
|
|
@@ -97,9 +97,10 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
97
97
|
} else if (category.id === 'memory') {
|
|
98
98
|
base.push(
|
|
99
99
|
{ text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
|
|
100
|
+
{ text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
100
101
|
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
|
|
101
102
|
{ text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
|
|
102
|
-
{ text: 'Durable memory, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
103
|
+
{ text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
103
104
|
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
104
105
|
);
|
|
105
106
|
} else if (category.id === 'work') {
|
|
@@ -6,7 +6,7 @@ import type { ProcessEntry } from './process-modal.ts';
|
|
|
6
6
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
7
7
|
|
|
8
8
|
export interface LiveTailModalDeps {
|
|
9
|
-
readonly agentManager: Pick<AgentManager, '
|
|
9
|
+
readonly agentManager: Pick<AgentManager, 'getStatus'>;
|
|
10
10
|
readonly processManager: Pick<ProcessManager, 'stop' | 'getOutput'>;
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ export interface LiveTailModalDeps {
|
|
|
15
15
|
/**
|
|
16
16
|
* LiveTailModal — manages state for the live output peek modal.
|
|
17
17
|
*
|
|
18
|
-
* Shows streaming stdout/stderr from a selected
|
|
18
|
+
* Shows streaming stdout/stderr from a selected shell process or agent
|
|
19
19
|
* progress notes. Auto-scrolls to the bottom unless the user scrolled up.
|
|
20
20
|
*/
|
|
21
21
|
export class LiveTailModal {
|
|
@@ -48,17 +48,17 @@ export class LiveTailModal {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* Stop the current shell process when it is an exec entry.
|
|
52
|
+
* Agent entries are read-only in GoodVibes Agent; build execution and
|
|
53
|
+
* cancellation belong to GoodVibes TUI/shared-session owners.
|
|
53
54
|
*/
|
|
54
55
|
killProcess(): boolean {
|
|
55
56
|
if (!this.entry) return false;
|
|
56
57
|
|
|
57
58
|
if (this.entry.type === 'exec') {
|
|
58
59
|
return this.deps.processManager.stop(this.entry.id);
|
|
59
|
-
} else {
|
|
60
|
-
return this.deps.agentManager.cancel(this.entry.id);
|
|
61
60
|
}
|
|
61
|
+
return false;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Retrieve the current output text for the watched process. */
|
|
@@ -151,6 +151,6 @@ export function renderLiveTailModal(
|
|
|
151
151
|
margin: 2,
|
|
152
152
|
targetContentRows,
|
|
153
153
|
sections,
|
|
154
|
-
hints: ['[Up/Down] Scroll', '[k]
|
|
154
|
+
hints: ['[Up/Down] Scroll', '[k] Stop exec only', '[Esc] Back'],
|
|
155
155
|
}, width);
|
|
156
156
|
}
|
|
@@ -17,12 +17,12 @@ function truncateToWidth(text: string, maxWidth: number): string {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* renderProcessIndicator — shows a one-line summary of active
|
|
21
|
-
*
|
|
20
|
+
* renderProcessIndicator — shows a one-line summary of active runtime
|
|
21
|
+
* activity below the input area.
|
|
22
22
|
*
|
|
23
|
-
* Dimmed when no
|
|
24
|
-
*
|
|
25
|
-
* when active.
|
|
23
|
+
* Dimmed when no entries are active, highlighted (cyan) when delegated agent
|
|
24
|
+
* records or shell exec processes are running. Includes an `Enter to view`
|
|
25
|
+
* hint when active.
|
|
26
26
|
*/
|
|
27
27
|
export function renderProcessIndicator(
|
|
28
28
|
width: number,
|
|
@@ -62,16 +62,16 @@ export function renderProcessIndicator(
|
|
|
62
62
|
if (agentCount > 0) parts.push(`${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
|
|
63
63
|
if (toolCount > 0) parts.push(`${toolCount} tool${toolCount !== 1 ? 's' : ''} running`);
|
|
64
64
|
const label = total === 0
|
|
65
|
-
? `No
|
|
65
|
+
? `No runtime activity ${GLYPHS.status.pending} back to input`
|
|
66
66
|
: `${parts.join(` ${GLYPHS.navigation.pipeSeparator} `)} ${GLYPHS.status.pending} Enter to open ${GLYPHS.status.pending} back to input`;
|
|
67
67
|
return renderFocusedStatus(label);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
if (total === 0) {
|
|
71
|
-
return renderPlainStatus('No
|
|
71
|
+
return renderPlainStatus('No runtime activity', { fg: '238', dim: true });
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// Build the label: "
|
|
74
|
+
// Build the label: "2 agents | Turn 3 | write - src/foo.ts"
|
|
75
75
|
const parts: string[] = [];
|
|
76
76
|
if (agentCount > 0) {
|
|
77
77
|
parts.push(`${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
|
|
@@ -42,7 +42,7 @@ const WRFC_ROLE_ORDER: Record<string, number> = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export interface ProcessModalDeps {
|
|
45
|
-
readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'
|
|
45
|
+
readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'>;
|
|
46
46
|
readonly processManager: Pick<ProcessManager, 'list' | 'getStatus' | 'stop'>;
|
|
47
47
|
readonly wrfcController: Pick<WrfcController, 'getChain'> & Partial<Pick<WrfcController, 'listChains'>>;
|
|
48
48
|
}
|
|
@@ -558,8 +558,9 @@ export class ProcessModal {
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
/**
|
|
561
|
-
*
|
|
562
|
-
*
|
|
561
|
+
* Stop the selected shell process.
|
|
562
|
+
* Agent entries are read-only in GoodVibes Agent; build execution and
|
|
563
|
+
* cancellation belong to GoodVibes TUI/shared-session owners.
|
|
563
564
|
*/
|
|
564
565
|
killSelected(): boolean {
|
|
565
566
|
const entry = this.getSelected();
|
|
@@ -567,9 +568,8 @@ export class ProcessModal {
|
|
|
567
568
|
|
|
568
569
|
if (entry.type === 'exec') {
|
|
569
570
|
return this.deps.processManager.stop(entry.id);
|
|
570
|
-
} else {
|
|
571
|
-
return this.deps.agentManager.cancel(entry.id);
|
|
572
571
|
}
|
|
572
|
+
return false;
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
|
|
@@ -598,12 +598,12 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
|
|
|
598
598
|
|
|
599
599
|
if (modal.entries.length === 0) {
|
|
600
600
|
return ModalFactory.createModal({
|
|
601
|
-
title: '
|
|
601
|
+
title: 'Runtime Activity',
|
|
602
602
|
width: boxW,
|
|
603
603
|
margin: boxMargin,
|
|
604
604
|
targetContentRows,
|
|
605
605
|
sections: [
|
|
606
|
-
{ type: 'text', content: 'No
|
|
606
|
+
{ type: 'text', content: 'No runtime activity.' },
|
|
607
607
|
],
|
|
608
608
|
hints: ['[Esc] Close'],
|
|
609
609
|
}, width);
|
|
@@ -643,7 +643,7 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
|
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
return ModalFactory.createModal({
|
|
646
|
-
title: '
|
|
646
|
+
title: 'Runtime Activity',
|
|
647
647
|
width: boxW,
|
|
648
648
|
margin: boxMargin,
|
|
649
649
|
targetContentRows,
|
|
@@ -651,6 +651,6 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
|
|
|
651
651
|
helpers: modal.entries.length > maxVisibleRows
|
|
652
652
|
? [{ content: `[${window.start + 1}-${window.end} of ${modal.entries.length}]` }]
|
|
653
653
|
: undefined,
|
|
654
|
-
hints: ['[Up/Down] Navigate', '[Enter]
|
|
654
|
+
hints: ['[Up/Down] Navigate', '[Enter] Details/output', '[k] Stop exec only', '[Esc] Close'],
|
|
655
655
|
}, width);
|
|
656
656
|
}
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { startMcpConfigAutoReload } from '../mcp/runtime-reload.ts';
|
|
|
44
44
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
45
45
|
import { buildActivePersonaPrompt } from '../agent/persona-registry.ts';
|
|
46
46
|
import { buildEnabledSkillsPrompt } from '../agent/skill-registry.ts';
|
|
47
|
+
import { buildEnabledRoutinesPrompt } from '../agent/routine-registry.ts';
|
|
47
48
|
|
|
48
49
|
const GOODVIBES_AGENT_OPERATOR_POLICY = [
|
|
49
50
|
'## GoodVibes Agent Operator Policy',
|
|
@@ -210,6 +211,7 @@ export async function bootstrapRuntime(
|
|
|
210
211
|
return joinPromptParts(
|
|
211
212
|
runtime.systemPrompt,
|
|
212
213
|
GOODVIBES_AGENT_OPERATOR_POLICY,
|
|
214
|
+
buildEnabledRoutinesPrompt(services.shellPaths),
|
|
213
215
|
buildEnabledSkillsPrompt(services.shellPaths),
|
|
214
216
|
buildActivePersonaPrompt(services.shellPaths),
|
|
215
217
|
supplement,
|
package/src/runtime/services.ts
CHANGED
|
@@ -88,7 +88,6 @@ import {
|
|
|
88
88
|
} from '@pellux/goodvibes-sdk/platform/tools';
|
|
89
89
|
import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
|
|
90
90
|
|
|
91
|
-
const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
|
|
92
91
|
const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
|
|
93
92
|
|
|
94
93
|
function buildFallbackModelDefinition(provider: string, modelId: string): ModelDefinition {
|
|
@@ -170,6 +169,7 @@ export interface RuntimeServices {
|
|
|
170
169
|
readonly automationManager: AutomationManager;
|
|
171
170
|
readonly gatewayMethods: GatewayMethodCatalog;
|
|
172
171
|
readonly artifactStore: ArtifactStore;
|
|
172
|
+
/** Compatibility alias that intentionally points at the isolated Agent Knowledge service, not default Knowledge/Wiki. */
|
|
173
173
|
readonly knowledgeService: KnowledgeService;
|
|
174
174
|
readonly agentKnowledgeService: KnowledgeService;
|
|
175
175
|
readonly homeGraphService: HomeGraphService;
|
|
@@ -395,10 +395,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
395
395
|
].join(' '));
|
|
396
396
|
},
|
|
397
397
|
});
|
|
398
|
-
const knowledgeStore = new KnowledgeStore({
|
|
399
|
-
configManager,
|
|
400
|
-
dbFileName: REGULAR_KNOWLEDGE_DB_FILE,
|
|
401
|
-
});
|
|
402
398
|
const agentKnowledgeStore = new KnowledgeStore({
|
|
403
399
|
configManager,
|
|
404
400
|
dbFileName: GOODVIBES_AGENT_KNOWLEDGE_DB_FILE,
|
|
@@ -411,10 +407,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
411
407
|
timeoutMs: 20_000,
|
|
412
408
|
maxConcurrent: 1,
|
|
413
409
|
});
|
|
414
|
-
const knowledgeSemanticService = new KnowledgeSemanticService(knowledgeStore, {
|
|
415
|
-
llm: knowledgeSemanticLlm,
|
|
416
|
-
maxLlmSourcesPerReindex: 3,
|
|
417
|
-
});
|
|
418
410
|
const homeGraphSemanticService = new KnowledgeSemanticService(homeGraphKnowledgeStore, {
|
|
419
411
|
llm: knowledgeSemanticLlm,
|
|
420
412
|
maxLlmSourcesPerReindex: 3,
|
|
@@ -424,12 +416,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
424
416
|
llm: knowledgeSemanticLlm,
|
|
425
417
|
maxLlmSourcesPerReindex: 3,
|
|
426
418
|
});
|
|
427
|
-
const knowledgeService = new KnowledgeService(knowledgeStore, artifactStore, undefined, {
|
|
428
|
-
memoryRegistry,
|
|
429
|
-
runtimeBus: options.runtimeBus,
|
|
430
|
-
semanticService: knowledgeSemanticService,
|
|
431
|
-
});
|
|
432
|
-
knowledgeService.attachRuntimeBus(options.runtimeBus);
|
|
433
419
|
const agentKnowledgeService = new KnowledgeService(agentKnowledgeStore, artifactStore, undefined, {
|
|
434
420
|
memoryRegistry,
|
|
435
421
|
runtimeBus: options.runtimeBus,
|
|
@@ -459,10 +445,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
459
445
|
serviceRegistry,
|
|
460
446
|
featureFlags,
|
|
461
447
|
});
|
|
462
|
-
knowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
|
|
463
|
-
searchService: webSearchService,
|
|
464
|
-
ingestService: knowledgeService,
|
|
465
|
-
}));
|
|
466
448
|
agentKnowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
|
|
467
449
|
searchService: webSearchService,
|
|
468
450
|
ingestService: agentKnowledgeService,
|
|
@@ -597,7 +579,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
597
579
|
automationManager,
|
|
598
580
|
gatewayMethods,
|
|
599
581
|
artifactStore,
|
|
600
|
-
knowledgeService,
|
|
582
|
+
knowledgeService: agentKnowledgeService,
|
|
601
583
|
agentKnowledgeService,
|
|
602
584
|
homeGraphService,
|
|
603
585
|
projectPlanningService,
|
|
@@ -10,6 +10,20 @@ type AgentToolPolicyGuardOptions = {
|
|
|
10
10
|
readonly getLastUserMessage?: () => string | null;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
const READ_ONLY_AGENT_TOOL_MODES = [
|
|
14
|
+
'status',
|
|
15
|
+
'list',
|
|
16
|
+
'templates',
|
|
17
|
+
'get',
|
|
18
|
+
'budget',
|
|
19
|
+
'wrfc-chains',
|
|
20
|
+
'wrfc-history',
|
|
21
|
+
'cohort-status',
|
|
22
|
+
'cohort-report',
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
const READ_ONLY_AGENT_TOOL_MODE_SET = new Set<string>(READ_ONLY_AGENT_TOOL_MODES);
|
|
26
|
+
|
|
13
27
|
const LOCAL_AGENT_DENIAL = [
|
|
14
28
|
'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
|
|
15
29
|
'Keep ordinary assistant work serial in the main conversation.',
|
|
@@ -23,6 +37,7 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
export function wrapAgentToolForAgentPolicy(tool: Tool, _options: AgentToolPolicyGuardOptions = {}): void {
|
|
40
|
+
narrowAgentToolDefinitionForAgentPolicy(tool);
|
|
26
41
|
const originalExecute = tool.execute.bind(tool);
|
|
27
42
|
tool.execute = async (args) => {
|
|
28
43
|
const denial = validateAgentToolInvocationForAgentPolicy(args as AgentToolArgs);
|
|
@@ -32,7 +47,7 @@ export function wrapAgentToolForAgentPolicy(tool: Tool, _options: AgentToolPolic
|
|
|
32
47
|
}
|
|
33
48
|
|
|
34
49
|
export function validateAgentToolInvocationForAgentPolicy(args: AgentToolArgs): string | null {
|
|
35
|
-
if (args.mode === '
|
|
50
|
+
if (typeof args.mode === 'string' && !READ_ONLY_AGENT_TOOL_MODE_SET.has(args.mode)) return LOCAL_AGENT_DENIAL;
|
|
36
51
|
return null;
|
|
37
52
|
}
|
|
38
53
|
|
|
@@ -41,6 +56,27 @@ export function normalizeAgentToolInvocationForAgentPolicy(args: AgentToolArgs):
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
export const AGENT_LOCAL_SPAWN_DENIAL_MESSAGE = LOCAL_AGENT_DENIAL;
|
|
59
|
+
export const AGENT_READ_ONLY_TOOL_MODES = READ_ONLY_AGENT_TOOL_MODES;
|
|
60
|
+
|
|
61
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
62
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function narrowAgentToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
66
|
+
tool.definition.description = [
|
|
67
|
+
'Read-only local Agent inspection for GoodVibes Agent.',
|
|
68
|
+
'This product does not spawn local worker agents or run local WRFC chains.',
|
|
69
|
+
'For build/fix/review work, delegate to GoodVibes TUI through the explicit build-delegation path instead.',
|
|
70
|
+
].join(' ');
|
|
71
|
+
tool.definition.sideEffects = [];
|
|
72
|
+
|
|
73
|
+
const properties = tool.definition.parameters.properties;
|
|
74
|
+
if (!isRecord(properties)) return;
|
|
75
|
+
const modeProperty = properties.mode;
|
|
76
|
+
if (!isRecord(modeProperty)) return;
|
|
77
|
+
modeProperty.enum = [...READ_ONLY_AGENT_TOOL_MODES];
|
|
78
|
+
modeProperty.description = 'Read-only Agent inspection mode. Local spawn, batch-spawn, cancel, message, wait, and plan modes are disabled in GoodVibes Agent.';
|
|
79
|
+
}
|
|
44
80
|
|
|
45
81
|
// Compatibility exports for copied TUI tests/imports during the near-fork phase.
|
|
46
82
|
export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.1.
|
|
9
|
+
let _version = '0.1.8';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: reviewer
|
|
3
|
-
description: Serial review of GoodVibes Agent changes against product policy, release gates, and TypeScript quality
|
|
4
|
-
tools: [read, find, analyze]
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
You review GoodVibes Agent work. Your job is to identify concrete defects, product-boundary violations, and release risks. You verify claims by inspecting files and cite exact paths.
|
|
8
|
-
|
|
9
|
-
## Review Priorities
|
|
10
|
-
|
|
11
|
-
1. Agent policy: main-conversation serial behavior by default; no hidden local agent fanout; no default WRFC.
|
|
12
|
-
2. Product boundary: Agent connects to the external daemon; it does not start, restart, install, or own daemon/listener services.
|
|
13
|
-
3. Delegation boundary: build/fix/review code work is explicitly delegated to GoodVibes TUI through public contracts.
|
|
14
|
-
4. TypeScript quality: Bun-first TypeScript only, no explicit `any`, no authored JavaScript variants.
|
|
15
|
-
5. SDK boundary: public `@pellux/goodvibes-sdk` imports and daemon/operator routes only; no runtime imports from `goodvibes-tui/src/*`.
|
|
16
|
-
6. Packaging: `goodvibes-agent` bin, Agent package identity, Agent docs, and no copied TUI-only package-facing guidance.
|
|
17
|
-
|
|
18
|
-
## Review Process
|
|
19
|
-
|
|
20
|
-
1. Read the completion report or changed-file list.
|
|
21
|
-
2. Inspect the files that define the behavior under review.
|
|
22
|
-
3. Check tests and release gates that should cover the change.
|
|
23
|
-
4. Report findings first, ordered by severity.
|
|
24
|
-
5. Keep summaries brief and secondary.
|
|
25
|
-
|
|
26
|
-
## Output Format
|
|
27
|
-
|
|
28
|
-
Use this structure:
|
|
29
|
-
|
|
30
|
-
```text
|
|
31
|
-
Findings
|
|
32
|
-
- severity: file:line - issue and impact
|
|
33
|
-
|
|
34
|
-
Open Questions
|
|
35
|
-
- question or assumption, if any
|
|
36
|
-
|
|
37
|
-
Validation Notes
|
|
38
|
-
- tests/gates reviewed or missing
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If there are no findings, say so clearly and list residual risk or missing validation.
|
|
42
|
-
|
|
43
|
-
## What You Do Not Do
|
|
44
|
-
|
|
45
|
-
- Do not modify code.
|
|
46
|
-
- Do not spawn other agents.
|
|
47
|
-
- Do not broaden scope beyond the reviewed slice.
|
|
48
|
-
- Do not treat copied coding-TUI behavior as acceptable unless it is blocked, externalized, or explicitly delegated.
|