@pellux/goodvibes-agent 0.1.7 → 0.1.9
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 +33 -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/agent-knowledge-command.ts +25 -2
- package/src/cli/help.ts +4 -4
- 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 +13 -12
- 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,20 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* /session command handler — Multi-session Orchestration.
|
|
3
3
|
*
|
|
4
|
-
* Implements session
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* — Register a task as a global cross-session ref, optionally linking it to a
|
|
8
|
-
* dependency.
|
|
9
|
-
*
|
|
10
|
-
* /session handoff <taskId> --to <sessionId> [--session <sessionId>] [--reason <reason>]
|
|
11
|
-
* — Initiate a task handoff from the current session to another.
|
|
12
|
-
*
|
|
13
|
-
* /session graph [--session <sessionId>] [--format text|json]
|
|
14
|
-
* — Display the cross-session task dependency graph.
|
|
15
|
-
*
|
|
16
|
-
* /session cancel <taskId|--scope session> [--session <sessionId>] [--scope task|subtree|session]
|
|
17
|
-
* — Cancel tasks with configurable scope semantics.
|
|
4
|
+
* Implements read-only session graph inspection plus session continuity commands.
|
|
5
|
+
* Copied local task graph mutation commands are blocked in Agent; explicit
|
|
6
|
+
* build/fix/review handoff must use `/delegate` so GoodVibes TUI owns execution.
|
|
18
7
|
*/
|
|
19
8
|
|
|
20
9
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
@@ -72,6 +61,14 @@ function fmtRef(ref: CrossSessionTaskRef): string {
|
|
|
72
61
|
return `${statusBadge(ref.status)} ${key}${label} "${ref.title}"`;
|
|
73
62
|
}
|
|
74
63
|
|
|
64
|
+
function printSessionGraphMutationBlocked(context: CommandContext): void {
|
|
65
|
+
context.print([
|
|
66
|
+
'[session] Local cross-session task graph mutation is blocked in GoodVibes Agent.',
|
|
67
|
+
'[session] Use /session graph for read-only inspection.',
|
|
68
|
+
'[session] Use /delegate <task> for explicit build/fix/review handoff to GoodVibes TUI.',
|
|
69
|
+
].join('\n'));
|
|
70
|
+
}
|
|
71
|
+
|
|
75
72
|
// ── /session link-task ────────────────────────────────────────────────────────
|
|
76
73
|
|
|
77
74
|
function handleLinkTask(args: string[], context: CommandContext): void {
|
|
@@ -192,7 +189,7 @@ function handleGraph(args: string[], context: CommandContext): void {
|
|
|
192
189
|
if (filterSession) {
|
|
193
190
|
context.print(`[session] No tasks registered for session ${filterSession.slice(0, 8)}...`);
|
|
194
191
|
} else {
|
|
195
|
-
context.print('[session] Task graph is empty.
|
|
192
|
+
context.print('[session] Task graph is empty. Local task graph mutation is blocked in Agent; use /delegate for explicit build/fix/review handoff.');
|
|
196
193
|
}
|
|
197
194
|
return;
|
|
198
195
|
}
|
|
@@ -324,21 +321,21 @@ function handleCancel(args: string[], context: CommandContext): void {
|
|
|
324
321
|
export const sessionCommand: SlashCommand = {
|
|
325
322
|
name: 'session',
|
|
326
323
|
aliases: ['sess'],
|
|
327
|
-
description: '
|
|
324
|
+
description: 'Session continuity and read-only cross-session graph inspection.',
|
|
328
325
|
usage: '<subcommand> [args]',
|
|
329
|
-
argsHint: '
|
|
326
|
+
argsHint: 'list|resume|save|graph',
|
|
330
327
|
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
331
328
|
const [sub, ...rest] = args;
|
|
332
329
|
|
|
333
330
|
switch (sub) {
|
|
334
331
|
case 'link-task':
|
|
335
332
|
case 'link':
|
|
336
|
-
|
|
333
|
+
printSessionGraphMutationBlocked(context);
|
|
337
334
|
break;
|
|
338
335
|
|
|
339
336
|
case 'handoff':
|
|
340
337
|
case 'ho':
|
|
341
|
-
|
|
338
|
+
printSessionGraphMutationBlocked(context);
|
|
342
339
|
break;
|
|
343
340
|
|
|
344
341
|
case 'graph':
|
|
@@ -347,7 +344,7 @@ export const sessionCommand: SlashCommand = {
|
|
|
347
344
|
break;
|
|
348
345
|
|
|
349
346
|
case 'cancel':
|
|
350
|
-
|
|
347
|
+
printSessionGraphMutationBlocked(context);
|
|
351
348
|
break;
|
|
352
349
|
|
|
353
350
|
default: {
|
|
@@ -357,14 +354,10 @@ export const sessionCommand: SlashCommand = {
|
|
|
357
354
|
'Usage: /session <subcommand>',
|
|
358
355
|
' list | rename <name> | resume <id|name> | fork [name] | save [name] | info [id] | export <id> [format] | search <query> | delete <id>',
|
|
359
356
|
' — Session continuity, export, resume, and pruning',
|
|
360
|
-
' link-task <taskId> [--session <sid>] [--depends-on <sid:taskId>] [--label <label>]',
|
|
361
|
-
' — Register a task in the cross-session graph',
|
|
362
|
-
' handoff <taskId> --to <sid> [--session <sid>] [--reason <reason>]',
|
|
363
|
-
' — Hand a task off to another session',
|
|
364
357
|
' graph [--session <sid>] [--format text|json]',
|
|
365
358
|
' — Display the cross-session task dependency graph',
|
|
366
|
-
'
|
|
367
|
-
' —
|
|
359
|
+
' link-task | handoff | cancel',
|
|
360
|
+
' — Blocked in Agent; use /delegate for explicit build/fix/review handoff',
|
|
368
361
|
].join('\n');
|
|
369
362
|
context.print(usage);
|
|
370
363
|
}
|
|
@@ -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 work
|
|
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,
|
|
@@ -32,6 +32,7 @@ export function renderProcessIndicator(
|
|
|
32
32
|
agentProgress?: string,
|
|
33
33
|
): Line[] {
|
|
34
34
|
const total = agentCount + toolCount;
|
|
35
|
+
const delegationLabel = (count: number): string => `${count} delegation${count !== 1 ? 's' : ''}`;
|
|
35
36
|
const renderPlainStatus = (text: string, style: { fg: string; bold?: boolean; dim?: boolean }): Line[] => (
|
|
36
37
|
[UIFactory.stringToLine(` ${text}`, width, style)]
|
|
37
38
|
);
|
|
@@ -59,30 +60,30 @@ export function renderProcessIndicator(
|
|
|
59
60
|
// --- Focused state: always render before idle/active branches ---
|
|
60
61
|
if (focused) {
|
|
61
62
|
const parts: string[] = [];
|
|
62
|
-
if (agentCount > 0) parts.push(
|
|
63
|
+
if (agentCount > 0) parts.push(delegationLabel(agentCount));
|
|
63
64
|
if (toolCount > 0) parts.push(`${toolCount} tool${toolCount !== 1 ? 's' : ''} running`);
|
|
64
65
|
const label = total === 0
|
|
65
|
-
? `No
|
|
66
|
+
? `No runtime activity ${GLYPHS.status.pending} back to input`
|
|
66
67
|
: `${parts.join(` ${GLYPHS.navigation.pipeSeparator} `)} ${GLYPHS.status.pending} Enter to open ${GLYPHS.status.pending} back to input`;
|
|
67
68
|
return renderFocusedStatus(label);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
if (total === 0) {
|
|
71
|
-
return renderPlainStatus('No
|
|
72
|
+
return renderPlainStatus('No runtime activity', { fg: '238', dim: true });
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
// Build the label: "
|
|
75
|
+
// Build the label: "2 delegations | Turn 3 | write - src/foo.ts"
|
|
75
76
|
const parts: string[] = [];
|
|
76
77
|
if (agentCount > 0) {
|
|
77
|
-
parts.push(
|
|
78
|
+
parts.push(delegationLabel(agentCount));
|
|
78
79
|
}
|
|
79
80
|
if (toolCount > 0) {
|
|
80
81
|
parts.push(`${toolCount} tool${toolCount !== 1 ? 's' : ''} running`);
|
|
81
82
|
}
|
|
82
83
|
// Append the first running agent's progress (truncated to fit)
|
|
83
84
|
/**
|
|
84
|
-
* Number of columns reserved for the
|
|
85
|
-
* Breakdown: "
|
|
85
|
+
* Number of columns reserved for the delegation count label and hint text.
|
|
86
|
+
* Breakdown: "N delegations" prefix (~15 chars) + " | " separator (~3)
|
|
86
87
|
* + " Enter to view " hint (~17) + padding (~8) ≈ 43 chars.
|
|
87
88
|
*/
|
|
88
89
|
const PROGRESS_RESERVED_CHARS = 43;
|
|
@@ -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;
|