@syntesseraai/opencode-feature-factory 0.2.14 → 0.2.16
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/dist/feature-factory-setup.js +85 -18
- package/dist/index.js +60 -10
- package/dist/plugins/ff-agents-clear-plugin.d.ts +2 -0
- package/dist/plugins/ff-agents-clear-plugin.js +55 -0
- package/dist/plugins/ff-agents-current-plugin.d.ts +2 -0
- package/dist/plugins/ff-agents-current-plugin.js +49 -0
- package/dist/plugins/ff-agents-show-plugin.d.ts +2 -0
- package/dist/plugins/ff-agents-show-plugin.js +26 -0
- package/dist/stop-quality-gate.js +20 -2
- package/package.json +1 -1
- package/dist/agent-management-tools.d.ts +0 -5
- package/dist/agent-management-tools.js +0 -117
|
@@ -18,6 +18,34 @@ async function log(client, level, message, extra) {
|
|
|
18
18
|
// Logging failure should not affect plugin operation
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Execute a shell command with a timeout to prevent hangs
|
|
23
|
+
*/
|
|
24
|
+
async function executeWithTimeout(operation, timeoutMs = 5000, operationName = 'shell command') {
|
|
25
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
26
|
+
setTimeout(() => reject(new Error(`${operationName} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
return await Promise.race([operation(), timeoutPromise]);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Return null on timeout or error
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate that a directory path is safe to use
|
|
38
|
+
*/
|
|
39
|
+
function isValidDirectory(directory) {
|
|
40
|
+
if (!directory || typeof directory !== 'string')
|
|
41
|
+
return false;
|
|
42
|
+
if (directory === '' || directory === '/')
|
|
43
|
+
return false;
|
|
44
|
+
// Check for common invalid patterns
|
|
45
|
+
if (directory.includes('\0') || directory.includes('..'))
|
|
46
|
+
return false;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
21
49
|
/**
|
|
22
50
|
* Initialize the Feature Factory directory structure.
|
|
23
51
|
* Creates .feature-factory/ and .feature-factory/agents/ directories.
|
|
@@ -25,11 +53,24 @@ async function log(client, level, message, extra) {
|
|
|
25
53
|
*/
|
|
26
54
|
export async function initializeFeatureFactory(input, $) {
|
|
27
55
|
const { directory, client } = input;
|
|
56
|
+
// Validate directory before attempting any operations
|
|
57
|
+
if (!isValidDirectory(directory)) {
|
|
58
|
+
await log(client, 'debug', 'feature-factory-init-skipped-invalid-directory', {
|
|
59
|
+
directory: String(directory),
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
28
63
|
const featureFactoryDir = `${directory}/.feature-factory`;
|
|
29
64
|
const agentsDir = `${featureFactoryDir}/agents`;
|
|
30
|
-
// Create directories if they don't exist
|
|
65
|
+
// Create directories if they don't exist (with timeout protection)
|
|
31
66
|
try {
|
|
32
|
-
await $ `mkdir -p ${agentsDir}`.quiet();
|
|
67
|
+
const result = await executeWithTimeout(async () => await $ `mkdir -p ${agentsDir}`.quiet(), 3000, 'mkdir -p');
|
|
68
|
+
if (result === null) {
|
|
69
|
+
await log(client, 'warn', 'feature-factory-directory-creation-timeout', {
|
|
70
|
+
agentsDir,
|
|
71
|
+
});
|
|
72
|
+
return; // Exit early if we can't create directories
|
|
73
|
+
}
|
|
33
74
|
await log(client, 'debug', 'feature-factory-directories-created', {
|
|
34
75
|
featureFactoryDir,
|
|
35
76
|
agentsDir,
|
|
@@ -41,44 +82,70 @@ export async function initializeFeatureFactory(input, $) {
|
|
|
41
82
|
});
|
|
42
83
|
// Continue even if directory creation fails - it might already exist
|
|
43
84
|
}
|
|
44
|
-
// Check for CI script migration
|
|
85
|
+
// Check for CI script migration (with timeout protection)
|
|
45
86
|
const oldCiPath = `${directory}/management/ci.sh`;
|
|
46
87
|
const newCiPath = `${featureFactoryDir}/ci.sh`;
|
|
47
88
|
try {
|
|
48
|
-
// Check if old CI exists
|
|
49
|
-
await
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
// Check if old CI exists (with timeout)
|
|
90
|
+
const oldExists = await executeWithTimeout(async () => {
|
|
91
|
+
await $ `test -f ${oldCiPath}`.quiet();
|
|
92
|
+
return true;
|
|
93
|
+
}, 2000, 'test -f old-ci');
|
|
94
|
+
if (oldExists !== true) {
|
|
95
|
+
// Old doesn't exist, nothing to migrate
|
|
96
|
+
await log(client, 'debug', 'ci-sh-no-migration-needed', {
|
|
97
|
+
oldPath: oldCiPath,
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Old exists, check if new already exists (with timeout)
|
|
102
|
+
const newExists = await executeWithTimeout(async () => {
|
|
52
103
|
await $ `test -f ${newCiPath}`.quiet();
|
|
104
|
+
return true;
|
|
105
|
+
}, 2000, 'test -f new-ci');
|
|
106
|
+
if (newExists === true) {
|
|
53
107
|
// Both exist - migration already done or user has both
|
|
54
108
|
await log(client, 'debug', 'ci-sh-both-locations-exist', {
|
|
55
109
|
oldPath: oldCiPath,
|
|
56
110
|
newPath: newCiPath,
|
|
57
111
|
});
|
|
112
|
+
return;
|
|
58
113
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
114
|
+
// Old exists, new doesn't - migrate (with timeout)
|
|
115
|
+
const copied = await executeWithTimeout(async () => {
|
|
116
|
+
await $ `cp ${oldCiPath} ${newCiPath}`.quiet();
|
|
117
|
+
return true;
|
|
118
|
+
}, 3000, 'cp ci.sh');
|
|
119
|
+
if (copied === true) {
|
|
120
|
+
const removed = await executeWithTimeout(async () => {
|
|
63
121
|
await $ `rm ${oldCiPath}`.quiet();
|
|
122
|
+
return true;
|
|
123
|
+
}, 2000, 'rm old-ci');
|
|
124
|
+
if (removed === true) {
|
|
64
125
|
await log(client, 'info', 'ci-sh-migrated', {
|
|
65
126
|
from: oldCiPath,
|
|
66
127
|
to: newCiPath,
|
|
67
128
|
});
|
|
68
129
|
}
|
|
69
|
-
|
|
70
|
-
await log(client, '
|
|
130
|
+
else {
|
|
131
|
+
await log(client, 'warn', 'ci-sh-migration-partial', {
|
|
71
132
|
from: oldCiPath,
|
|
72
133
|
to: newCiPath,
|
|
73
|
-
|
|
134
|
+
reason: 'copied but failed to remove old file',
|
|
74
135
|
});
|
|
75
136
|
}
|
|
76
137
|
}
|
|
138
|
+
else {
|
|
139
|
+
await log(client, 'error', 'ci-sh-migration-failed', {
|
|
140
|
+
from: oldCiPath,
|
|
141
|
+
to: newCiPath,
|
|
142
|
+
reason: 'copy operation timed out or failed',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
77
145
|
}
|
|
78
|
-
catch {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
oldPath: oldCiPath,
|
|
146
|
+
catch (error) {
|
|
147
|
+
await log(client, 'error', 'ci-sh-migration-error', {
|
|
148
|
+
error: String(error),
|
|
82
149
|
});
|
|
83
150
|
}
|
|
84
151
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { createQualityGateHooks } from './stop-quality-gate.js';
|
|
2
2
|
import { initializeFeatureFactory } from './feature-factory-setup.js';
|
|
3
|
-
import {
|
|
3
|
+
import { FFAgentsCurrentPlugin } from './plugins/ff-agents-current-plugin.js';
|
|
4
|
+
import { FFAgentsClearPlugin } from './plugins/ff-agents-clear-plugin.js';
|
|
5
|
+
import { FFAgentsShowPlugin } from './plugins/ff-agents-show-plugin.js';
|
|
4
6
|
const SERVICE_NAME = 'feature-factory';
|
|
7
|
+
const PLUGIN_INIT_TIMEOUT_MS = 10000; // 10 seconds max for plugin initialization
|
|
8
|
+
/**
|
|
9
|
+
* Wrap an async operation with a timeout to prevent hangs
|
|
10
|
+
*/
|
|
11
|
+
async function withTimeout(operation, timeoutMs, _context) {
|
|
12
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
13
|
+
setTimeout(() => reject(new Error(`timeout`)), timeoutMs);
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
return await Promise.race([operation(), timeoutPromise]);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* Log a message using the OpenCode client's structured logging.
|
|
7
24
|
* Silently fails if logging is unavailable.
|
|
@@ -56,38 +73,71 @@ export const StopQualityGatePlugin = async (input) => {
|
|
|
56
73
|
if (!rootDir || rootDir === '' || rootDir === '/') {
|
|
57
74
|
return {};
|
|
58
75
|
}
|
|
59
|
-
// Initialize Feature Factory directory structure
|
|
76
|
+
// Initialize Feature Factory directory structure (with timeout protection)
|
|
60
77
|
try {
|
|
61
|
-
await initializeFeatureFactory(input, $);
|
|
78
|
+
const initResult = await withTimeout(async () => await initializeFeatureFactory(input, $), PLUGIN_INIT_TIMEOUT_MS, 'feature-factory-init');
|
|
79
|
+
if (initResult === null) {
|
|
80
|
+
await log(client, 'warn', 'feature-factory-init-timeout', {
|
|
81
|
+
timeoutMs: PLUGIN_INIT_TIMEOUT_MS,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
62
84
|
}
|
|
63
85
|
catch (error) {
|
|
64
86
|
await log(client, 'error', 'feature-factory-init-error', {
|
|
65
87
|
error: String(error),
|
|
66
88
|
});
|
|
67
89
|
}
|
|
68
|
-
// Create quality gate hooks
|
|
90
|
+
// Create quality gate hooks (with timeout protection)
|
|
69
91
|
let qualityGateHooks = {};
|
|
70
92
|
try {
|
|
71
|
-
|
|
93
|
+
const hooksResult = await withTimeout(async () => await createQualityGateHooks(input), PLUGIN_INIT_TIMEOUT_MS, 'quality-gate-init');
|
|
94
|
+
if (hooksResult !== null) {
|
|
95
|
+
qualityGateHooks = hooksResult;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await log(client, 'warn', 'quality-gate-init-timeout', {
|
|
99
|
+
timeoutMs: PLUGIN_INIT_TIMEOUT_MS,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
72
102
|
}
|
|
73
103
|
catch (error) {
|
|
74
104
|
await log(client, 'error', 'quality-gate.init-error', {
|
|
75
105
|
error: String(error),
|
|
76
106
|
});
|
|
77
107
|
}
|
|
78
|
-
// Create agent management
|
|
79
|
-
let
|
|
108
|
+
// Create agent management tool plugins
|
|
109
|
+
let agentsCurrentHooks = {};
|
|
110
|
+
let agentsClearHooks = {};
|
|
111
|
+
let agentsShowHooks = {};
|
|
112
|
+
try {
|
|
113
|
+
agentsCurrentHooks = await FFAgentsCurrentPlugin(input);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
await log(client, 'error', 'ff-agents-current.init-error', {
|
|
117
|
+
error: String(error),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
agentsClearHooks = await FFAgentsClearPlugin(input);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
await log(client, 'error', 'ff-agents-clear.init-error', {
|
|
125
|
+
error: String(error),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
80
128
|
try {
|
|
81
|
-
|
|
129
|
+
agentsShowHooks = await FFAgentsShowPlugin(input);
|
|
82
130
|
}
|
|
83
131
|
catch (error) {
|
|
84
|
-
await log(client, 'error', '
|
|
132
|
+
await log(client, 'error', 'ff-agents-show.init-error', {
|
|
85
133
|
error: String(error),
|
|
86
134
|
});
|
|
87
135
|
}
|
|
88
136
|
return {
|
|
89
137
|
...qualityGateHooks,
|
|
90
|
-
...
|
|
138
|
+
...agentsCurrentHooks,
|
|
139
|
+
...agentsClearHooks,
|
|
140
|
+
...agentsShowHooks,
|
|
91
141
|
};
|
|
92
142
|
};
|
|
93
143
|
// Default export for OpenCode plugin discovery
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import { findAgentFiles, findAgentFilesById, findAllAgentFiles } from '../agent-context.js';
|
|
3
|
+
export const FFAgentsClearPlugin = async (input) => {
|
|
4
|
+
const { $ } = input;
|
|
5
|
+
return {
|
|
6
|
+
tool: {
|
|
7
|
+
'ff-agents-clear': tool({
|
|
8
|
+
description: 'Clear agent context files. Can clear all, or filter by session, agent type, or specific UUID',
|
|
9
|
+
args: {
|
|
10
|
+
sessionID: tool.schema
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Clear only agents for specific session'),
|
|
14
|
+
agent: tool.schema.string().optional().describe('Clear only specific agent type'),
|
|
15
|
+
id: tool.schema.string().optional().describe('Clear specific agent by UUID'),
|
|
16
|
+
},
|
|
17
|
+
async execute(args) {
|
|
18
|
+
try {
|
|
19
|
+
let files = [];
|
|
20
|
+
if (args.id) {
|
|
21
|
+
files = await findAgentFilesById(input, args.id);
|
|
22
|
+
}
|
|
23
|
+
else if (args.agent && args.sessionID) {
|
|
24
|
+
files = await findAgentFiles(input, args.agent, args.sessionID);
|
|
25
|
+
}
|
|
26
|
+
else if (args.sessionID) {
|
|
27
|
+
files = await findAgentFiles(input, undefined, args.sessionID);
|
|
28
|
+
}
|
|
29
|
+
else if (args.agent) {
|
|
30
|
+
files = await findAgentFiles(input, args.agent);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
files = await findAllAgentFiles(input);
|
|
34
|
+
}
|
|
35
|
+
if (files.length === 0) {
|
|
36
|
+
return 'No agent context files found to clear.';
|
|
37
|
+
}
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
try {
|
|
40
|
+
await $ `rm ${file}`.quiet();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Continue even if one file fails
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return `Cleared ${files.length} agent context file(s)`;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return `Error clearing agents: ${error}`;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import { listActiveAgents } from '../agent-context.js';
|
|
3
|
+
export const FFAgentsCurrentPlugin = async (input) => {
|
|
4
|
+
return {
|
|
5
|
+
tool: {
|
|
6
|
+
'ff-agents-current': tool({
|
|
7
|
+
description: 'List all currently active Feature Factory agents with their UUIDs and status',
|
|
8
|
+
args: {
|
|
9
|
+
sessionID: tool.schema.string().optional().describe('Filter by specific session ID'),
|
|
10
|
+
agent: tool.schema
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Filter by agent type (e.g., planning, research)'),
|
|
14
|
+
},
|
|
15
|
+
async execute(args) {
|
|
16
|
+
try {
|
|
17
|
+
const agents = await listActiveAgents(input, args.sessionID, args.agent);
|
|
18
|
+
if (agents.length === 0) {
|
|
19
|
+
return JSON.stringify({
|
|
20
|
+
count: 0,
|
|
21
|
+
message: 'No active agents found',
|
|
22
|
+
agents: [],
|
|
23
|
+
}, null, 2);
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify({
|
|
26
|
+
count: agents.length,
|
|
27
|
+
agents: agents.map((a) => ({
|
|
28
|
+
id: a.id,
|
|
29
|
+
agent: a.agent,
|
|
30
|
+
title: a.title,
|
|
31
|
+
folder: a.folder,
|
|
32
|
+
status: a.status,
|
|
33
|
+
started: a.started,
|
|
34
|
+
session: a.session,
|
|
35
|
+
parent: a.parent || null,
|
|
36
|
+
delegated_to: a.delegated_to || [],
|
|
37
|
+
})),
|
|
38
|
+
}, null, 2);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
error: `Failed to list agents: ${error}`,
|
|
43
|
+
}, null, 2);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import { readAgentContextById } from '../agent-context.js';
|
|
3
|
+
export const FFAgentsShowPlugin = async (input) => {
|
|
4
|
+
return {
|
|
5
|
+
tool: {
|
|
6
|
+
'ff-agents-show': tool({
|
|
7
|
+
description: 'Show detailed context for a specific agent by UUID',
|
|
8
|
+
args: {
|
|
9
|
+
id: tool.schema.string().describe('Agent UUID to show details for'),
|
|
10
|
+
},
|
|
11
|
+
async execute(args) {
|
|
12
|
+
try {
|
|
13
|
+
const agentContext = await readAgentContextById(input, args.id);
|
|
14
|
+
if (!agentContext) {
|
|
15
|
+
return `Agent with ID "${args.id}" not found`;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(agentContext, null, 2);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return `Error reading agent: ${error}`;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -154,12 +154,30 @@ async function log(client, level, message, extra) {
|
|
|
154
154
|
return undefined;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Execute a shell command with a timeout to prevent hangs
|
|
159
|
+
*/
|
|
160
|
+
async function executeWithTimeout(operation, timeoutMs = 5000, _operationName = 'shell command') {
|
|
161
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
162
|
+
setTimeout(() => reject(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
163
|
+
});
|
|
164
|
+
try {
|
|
165
|
+
return await Promise.race([operation(), timeoutPromise]);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Return null on timeout or error
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
157
172
|
export async function createQualityGateHooks(input) {
|
|
158
173
|
const { client, $, directory } = input;
|
|
159
174
|
async function ciShExists() {
|
|
160
175
|
try {
|
|
161
|
-
await
|
|
162
|
-
|
|
176
|
+
const result = await executeWithTimeout(async () => {
|
|
177
|
+
await $ `test -f ${directory}/.feature-factory/ci.sh`.quiet();
|
|
178
|
+
return true;
|
|
179
|
+
}, 2000, 'test -f ci.sh');
|
|
180
|
+
return result === true;
|
|
163
181
|
}
|
|
164
182
|
catch {
|
|
165
183
|
return false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@syntesseraai/opencode-feature-factory",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.16",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "OpenCode plugin for Feature Factory agents - provides sub-agents and skills for validation, review, security, and architecture assessment",
|
|
7
7
|
"license": "MIT",
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin';
|
|
2
|
-
import { listActiveAgents, findAgentFiles, findAgentFilesById, findAllAgentFiles, readAgentContextById, } from './agent-context.js';
|
|
3
|
-
/**
|
|
4
|
-
* Create agent management tools for the plugin
|
|
5
|
-
*/
|
|
6
|
-
export function createAgentManagementTools(input) {
|
|
7
|
-
const { $ } = input;
|
|
8
|
-
return {
|
|
9
|
-
tool: {
|
|
10
|
-
'ff-agents-current': tool({
|
|
11
|
-
description: 'List all currently active Feature Factory agents with their UUIDs and status',
|
|
12
|
-
args: {
|
|
13
|
-
sessionID: tool.schema.string().optional().describe('Filter by specific session ID'),
|
|
14
|
-
agent: tool.schema
|
|
15
|
-
.string()
|
|
16
|
-
.optional()
|
|
17
|
-
.describe('Filter by agent type (e.g., planning, research)'),
|
|
18
|
-
},
|
|
19
|
-
execute: async (args) => {
|
|
20
|
-
try {
|
|
21
|
-
const agents = await listActiveAgents(input, args.sessionID, args.agent);
|
|
22
|
-
if (agents.length === 0) {
|
|
23
|
-
return JSON.stringify({
|
|
24
|
-
count: 0,
|
|
25
|
-
message: 'No active agents found',
|
|
26
|
-
agents: [],
|
|
27
|
-
}, null, 2);
|
|
28
|
-
}
|
|
29
|
-
return JSON.stringify({
|
|
30
|
-
count: agents.length,
|
|
31
|
-
agents: agents.map((a) => ({
|
|
32
|
-
id: a.id,
|
|
33
|
-
agent: a.agent,
|
|
34
|
-
title: a.title,
|
|
35
|
-
folder: a.folder,
|
|
36
|
-
status: a.status,
|
|
37
|
-
started: a.started,
|
|
38
|
-
session: a.session,
|
|
39
|
-
parent: a.parent || null,
|
|
40
|
-
delegated_to: a.delegated_to || [],
|
|
41
|
-
})),
|
|
42
|
-
}, null, 2);
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
return JSON.stringify({
|
|
46
|
-
error: `Failed to list agents: ${error}`,
|
|
47
|
-
}, null, 2);
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
}),
|
|
51
|
-
'ff-agents-clear': tool({
|
|
52
|
-
description: 'Clear agent context files. Can clear all, or filter by session, agent type, or specific UUID',
|
|
53
|
-
args: {
|
|
54
|
-
sessionID: tool.schema
|
|
55
|
-
.string()
|
|
56
|
-
.optional()
|
|
57
|
-
.describe('Clear only agents for specific session'),
|
|
58
|
-
agent: tool.schema.string().optional().describe('Clear only specific agent type'),
|
|
59
|
-
id: tool.schema.string().optional().describe('Clear specific agent by UUID'),
|
|
60
|
-
},
|
|
61
|
-
execute: async (args) => {
|
|
62
|
-
try {
|
|
63
|
-
let files = [];
|
|
64
|
-
if (args.id) {
|
|
65
|
-
files = await findAgentFilesById(input, args.id);
|
|
66
|
-
}
|
|
67
|
-
else if (args.agent && args.sessionID) {
|
|
68
|
-
files = await findAgentFiles(input, args.agent, args.sessionID);
|
|
69
|
-
}
|
|
70
|
-
else if (args.sessionID) {
|
|
71
|
-
files = await findAgentFiles(input, undefined, args.sessionID);
|
|
72
|
-
}
|
|
73
|
-
else if (args.agent) {
|
|
74
|
-
files = await findAgentFiles(input, args.agent);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
files = await findAllAgentFiles(input);
|
|
78
|
-
}
|
|
79
|
-
if (files.length === 0) {
|
|
80
|
-
return 'No agent context files found to clear.';
|
|
81
|
-
}
|
|
82
|
-
for (const file of files) {
|
|
83
|
-
try {
|
|
84
|
-
await $ `rm ${file}`.quiet();
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Continue even if one file fails
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return `Cleared ${files.length} agent context file(s)`;
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
return `Error clearing agents: ${error}`;
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
'ff-agents-show': tool({
|
|
98
|
-
description: 'Show detailed context for a specific agent by UUID',
|
|
99
|
-
args: {
|
|
100
|
-
id: tool.schema.string().describe('Agent UUID to show details for'),
|
|
101
|
-
},
|
|
102
|
-
execute: async (args) => {
|
|
103
|
-
try {
|
|
104
|
-
const agentContext = await readAgentContextById(input, args.id);
|
|
105
|
-
if (!agentContext) {
|
|
106
|
-
return `Agent with ID "${args.id}" not found`;
|
|
107
|
-
}
|
|
108
|
-
return JSON.stringify(agentContext, null, 2);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
return `Error reading agent: ${error}`;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
}),
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|