@amaster.ai/pi-teamwork 0.1.2-beta.9 → 0.1.3
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/README.md +85 -8
- package/dist/adapters/amaster.d.ts +35 -0
- package/dist/adapters/amaster.d.ts.map +1 -0
- package/dist/adapters/amaster.js +420 -0
- package/dist/adapters/amaster.js.map +1 -0
- package/dist/adapters/multica-installer.d.ts +8 -0
- package/dist/adapters/multica-installer.d.ts.map +1 -0
- package/dist/adapters/multica-installer.js +103 -0
- package/dist/adapters/multica-installer.js.map +1 -0
- package/dist/adapters/multica.d.ts +15 -8
- package/dist/adapters/multica.d.ts.map +1 -1
- package/dist/adapters/multica.js +75 -20
- package/dist/adapters/multica.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +437 -160
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +37 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/preview.png +0 -0
package/dist/index.js
CHANGED
|
@@ -1,35 +1,165 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { loadPiSettings } from '@amaster.ai/pi-shared/settings';
|
|
3
|
+
import { defineTool } from '@earendil-works/pi-coding-agent';
|
|
2
4
|
import { Type } from 'typebox';
|
|
5
|
+
import { initAmasterProvider } from './adapters/amaster.js';
|
|
3
6
|
import { initMulticaProvider } from './adapters/multica.js';
|
|
4
7
|
const SETTINGS_KEY = 'pi-teamwork';
|
|
5
8
|
const STATUS_KEY = 'pi-teamwork';
|
|
9
|
+
const TEAMWORK_TOOL_ORDER = [
|
|
10
|
+
'workspace_list',
|
|
11
|
+
'issue_list',
|
|
12
|
+
'issue_get',
|
|
13
|
+
'issue_create',
|
|
14
|
+
'issue_update',
|
|
15
|
+
'issue_comment',
|
|
16
|
+
'project_list',
|
|
17
|
+
'user_directory_list',
|
|
18
|
+
'teamwork_status',
|
|
19
|
+
];
|
|
20
|
+
const TEAMWORK_TOOL_NAMES = new Set(TEAMWORK_TOOL_ORDER);
|
|
21
|
+
const TEAMWORK_GUIDANCE = [
|
|
22
|
+
'<teamwork-guidance>',
|
|
23
|
+
'You are working in a shared project tracker (teamwork) where humans and other agents collaborate. Use the issue_* and project_* tools to keep that shared space in sync.',
|
|
24
|
+
'',
|
|
25
|
+
'Before starting non-trivial work, check whether a relevant issue already exists (issue_list / issue_get) — to avoid duplicating work another collaborator has picked up.',
|
|
26
|
+
'',
|
|
27
|
+
'When you reach meaningful progress, hit a blocker, or need input from a collaborator, leave a comment on the relevant issue (issue_comment). Comments are how humans and other agents observe what you are doing.',
|
|
28
|
+
'',
|
|
29
|
+
'When you finish work that an issue describes, update its status (issue_update). An issue left in an outdated state misleads other collaborators.',
|
|
30
|
+
'',
|
|
31
|
+
'The tracker is for cross-collaborator coordination, not for tracking your own session-local TODOs. Do not file an issue just to remind yourself of something within the current conversation.',
|
|
32
|
+
'</teamwork-guidance>',
|
|
33
|
+
].join('\n');
|
|
6
34
|
export default function piTeamworkExtension(pi) {
|
|
7
35
|
let provider;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
36
|
+
let readyPromise;
|
|
37
|
+
let sessionGeneration = 0;
|
|
38
|
+
const registeredToolNames = new Set();
|
|
39
|
+
const suspendedToolNames = new Set();
|
|
40
|
+
async function ensureReady() {
|
|
41
|
+
if (readyPromise)
|
|
42
|
+
await readyPromise;
|
|
43
|
+
if (!provider)
|
|
44
|
+
return 'Teamwork provider is not initialized.';
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
function getProvider() {
|
|
48
|
+
if (!provider)
|
|
49
|
+
throw new Error('Teamwork provider is not initialized.');
|
|
50
|
+
return provider;
|
|
51
|
+
}
|
|
52
|
+
const teamworkTools = createTeamworkTools(ensureReady, getProvider);
|
|
53
|
+
function registerProviderTools(adapter) {
|
|
54
|
+
const supportedToolNames = supportedToolNamesForProvider(adapter);
|
|
55
|
+
for (const tool of teamworkTools) {
|
|
56
|
+
if (!supportedToolNames.has(tool.name) || registeredToolNames.has(tool.name)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
pi.registerTool(tool);
|
|
60
|
+
registeredToolNames.add(tool.name);
|
|
61
|
+
}
|
|
62
|
+
alignActiveTeamworkTools(pi, supportedToolNames, suspendedToolNames);
|
|
63
|
+
}
|
|
64
|
+
function suspendTeamworkTools() {
|
|
65
|
+
alignActiveTeamworkTools(pi, new Set(), suspendedToolNames);
|
|
66
|
+
}
|
|
12
67
|
pi.on('session_start', async (_event, ctx) => {
|
|
68
|
+
const generation = ++sessionGeneration;
|
|
69
|
+
provider = undefined;
|
|
70
|
+
readyPromise = undefined;
|
|
13
71
|
const config = loadConfig(ctx.cwd);
|
|
14
72
|
if (config.enabled === false) {
|
|
73
|
+
suspendTeamworkTools();
|
|
15
74
|
ctx.ui.setStatus(STATUS_KEY, 'teamwork: disabled');
|
|
16
75
|
return;
|
|
17
76
|
}
|
|
77
|
+
const exec = async (command, args) => {
|
|
78
|
+
const result = await pi.exec(command, args, { cwd: ctx.cwd });
|
|
79
|
+
return { stdout: result.stdout, stderr: result.stderr, code: result.code };
|
|
80
|
+
};
|
|
18
81
|
const providerName = config.provider ?? 'multica';
|
|
19
|
-
if (providerName === '
|
|
20
|
-
|
|
21
|
-
|
|
82
|
+
if (providerName === 'amaster') {
|
|
83
|
+
const amasterConfig = { ...(config.amaster ?? {}) };
|
|
84
|
+
const eventRecord = _event && typeof _event === 'object' && !Array.isArray(_event)
|
|
85
|
+
? _event
|
|
86
|
+
: undefined;
|
|
87
|
+
const amasterEmployee = eventRecord?.amasterEmployee;
|
|
88
|
+
const sessionApiKey = amasterEmployee && typeof amasterEmployee === 'object' && !Array.isArray(amasterEmployee)
|
|
89
|
+
? amasterEmployee.apiKey
|
|
90
|
+
: undefined;
|
|
91
|
+
const apiKey = typeof sessionApiKey === 'string' && sessionApiKey.trim()
|
|
92
|
+
? sessionApiKey.trim()
|
|
93
|
+
: typeof amasterConfig.apiKey === 'string' && amasterConfig.apiKey.trim()
|
|
94
|
+
? amasterConfig.apiKey.trim()
|
|
95
|
+
: undefined;
|
|
96
|
+
delete amasterConfig.apiKey;
|
|
97
|
+
const amasterExec = apiKey
|
|
98
|
+
? (command, args) => command === 'amaster-employee'
|
|
99
|
+
? execWithEnv(command, args, { AMASTER_BOARD_API_KEY: apiKey }, ctx.cwd)
|
|
100
|
+
: exec(command, args)
|
|
101
|
+
: exec;
|
|
102
|
+
const adapter = await initAmasterProvider(amasterConfig, amasterExec);
|
|
103
|
+
if (generation !== sessionGeneration)
|
|
104
|
+
return;
|
|
105
|
+
provider = adapter;
|
|
106
|
+
registerProviderTools(adapter);
|
|
107
|
+
ctx.ui.setStatus(STATUS_KEY, 'teamwork: amaster');
|
|
108
|
+
return;
|
|
22
109
|
}
|
|
23
|
-
|
|
110
|
+
if (providerName !== 'multica') {
|
|
111
|
+
suspendTeamworkTools();
|
|
24
112
|
ctx.ui.setStatus(STATUS_KEY, `teamwork: unknown provider "${providerName}"`);
|
|
113
|
+
return;
|
|
25
114
|
}
|
|
115
|
+
readyPromise = (async () => {
|
|
116
|
+
const { adapter, installResult } = await initMulticaProvider(config.multica ?? {}, exec);
|
|
117
|
+
if (generation !== sessionGeneration)
|
|
118
|
+
return;
|
|
119
|
+
provider = adapter;
|
|
120
|
+
registerProviderTools(adapter);
|
|
121
|
+
if (!installResult.installed) {
|
|
122
|
+
ctx.ui.notify(`multica CLI could not be installed: ${installResult.error ?? 'unknown error'}. Run "multica setup" manually.`, 'warning');
|
|
123
|
+
ctx.ui.setStatus(STATUS_KEY, 'teamwork: multica (not installed)');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!installResult.alreadyPresent) {
|
|
127
|
+
ctx.ui.notify('multica CLI was auto-installed successfully.', 'info');
|
|
128
|
+
}
|
|
129
|
+
if (!config.multica?.token && !config.multica?.serverUrl) {
|
|
130
|
+
ctx.ui.notify('No multica token or serverUrl configured. Run "multica setup" to authenticate.', 'warning');
|
|
131
|
+
}
|
|
132
|
+
if (config.multica?.serverUrl && !config.multica?.appUrl) {
|
|
133
|
+
ctx.ui.notify('multica serverUrl is set but appUrl is missing. Remote servers require both.', 'warning');
|
|
134
|
+
}
|
|
135
|
+
ctx.ui.setStatus(STATUS_KEY, 'teamwork: multica');
|
|
136
|
+
})().catch((err) => {
|
|
137
|
+
if (generation !== sessionGeneration)
|
|
138
|
+
return;
|
|
139
|
+
ctx.ui.setStatus(STATUS_KEY, `teamwork: multica (error: ${err instanceof Error ? err.message : String(err)})`);
|
|
140
|
+
});
|
|
26
141
|
});
|
|
27
142
|
pi.on('session_shutdown', async () => {
|
|
143
|
+
sessionGeneration++;
|
|
28
144
|
provider = undefined;
|
|
145
|
+
readyPromise = undefined;
|
|
146
|
+
});
|
|
147
|
+
pi.on('before_agent_start', async (event) => {
|
|
148
|
+
if (readyPromise)
|
|
149
|
+
await readyPromise;
|
|
150
|
+
if (!provider)
|
|
151
|
+
return;
|
|
152
|
+
return {
|
|
153
|
+
systemPrompt: event.systemPrompt
|
|
154
|
+
? `${event.systemPrompt}\n\n${TEAMWORK_GUIDANCE}`
|
|
155
|
+
: TEAMWORK_GUIDANCE,
|
|
156
|
+
};
|
|
29
157
|
});
|
|
30
158
|
pi.registerCommand('teamwork-status', {
|
|
31
159
|
description: 'Show teamwork provider status.',
|
|
32
160
|
handler: async (_args, ctx) => {
|
|
161
|
+
if (readyPromise)
|
|
162
|
+
await readyPromise;
|
|
33
163
|
if (!provider) {
|
|
34
164
|
ctx.ui.notify('Teamwork provider is not initialized.', 'warning');
|
|
35
165
|
return;
|
|
@@ -43,172 +173,319 @@ export default function piTeamworkExtension(pi) {
|
|
|
43
173
|
}
|
|
44
174
|
},
|
|
45
175
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
176
|
+
}
|
|
177
|
+
function createTeamworkTools(ensureReady, getProvider) {
|
|
178
|
+
return [
|
|
179
|
+
defineTool({
|
|
180
|
+
name: 'workspace_list',
|
|
181
|
+
label: 'Teamwork',
|
|
182
|
+
description: 'List all workspaces you belong to. Use this to discover or switch workspaces. For the AMaster provider, workspaceId is the canonical AMaster companyId passed to the CLI as -C.',
|
|
183
|
+
promptSnippet: 'List workspaces in the shared project tracker.',
|
|
184
|
+
parameters: Type.Object({}),
|
|
185
|
+
async execute() {
|
|
186
|
+
const err = await ensureReady();
|
|
187
|
+
if (err)
|
|
188
|
+
return textResult(err);
|
|
189
|
+
try {
|
|
190
|
+
const workspaces = await getProvider().listWorkspaces();
|
|
191
|
+
if (workspaces.length === 0)
|
|
192
|
+
return textResult('No workspaces found.');
|
|
193
|
+
return textResult(JSON.stringify(workspaces, null, 2));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
57
199
|
}),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
200
|
+
defineTool({
|
|
201
|
+
name: 'issue_list',
|
|
202
|
+
label: 'Teamwork',
|
|
203
|
+
description: 'List issues from a shared project tracker. Issues are work items that humans or other agents collaborate on. Supports filtering by status, assignee, and project.',
|
|
204
|
+
promptSnippet: 'List issues from a shared project tracker where humans and agents collaborate.',
|
|
205
|
+
parameters: Type.Object({
|
|
206
|
+
workspaceId: Type.Optional(Type.String({
|
|
207
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
208
|
+
})),
|
|
209
|
+
status: Type.Optional(Type.String({ description: 'Filter by status (e.g. todo, in_progress, done, blocked).' })),
|
|
210
|
+
assignee: Type.Optional(Type.String({ description: 'Filter by assignee name.' })),
|
|
211
|
+
project: Type.Optional(Type.String({ description: 'Filter by project ID.' })),
|
|
212
|
+
limit: Type.Optional(Type.Number({ description: 'Max number of results.' })),
|
|
213
|
+
}),
|
|
214
|
+
async execute(_toolCallId, params) {
|
|
215
|
+
const err = await ensureReady();
|
|
216
|
+
if (err)
|
|
217
|
+
return textResult(err);
|
|
218
|
+
try {
|
|
219
|
+
const issues = await getProvider().listIssues(params);
|
|
220
|
+
if (issues.length === 0)
|
|
221
|
+
return textResult('No issues found.');
|
|
222
|
+
return textResult(JSON.stringify(issues, null, 2));
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
78
228
|
}),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
project: Type.Optional(Type.String({ description: 'Project ID to associate with.' })),
|
|
229
|
+
defineTool({
|
|
230
|
+
name: 'issue_get',
|
|
231
|
+
label: 'Teamwork',
|
|
232
|
+
description: 'Get detailed information about a specific issue (a work item in the shared project tracker).',
|
|
233
|
+
parameters: Type.Object({
|
|
234
|
+
workspaceId: Type.Optional(Type.String({
|
|
235
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
236
|
+
})),
|
|
237
|
+
id: Type.String({ description: 'The issue ID.' }),
|
|
238
|
+
}),
|
|
239
|
+
async execute(_toolCallId, params) {
|
|
240
|
+
const err = await ensureReady();
|
|
241
|
+
if (err)
|
|
242
|
+
return textResult(err);
|
|
243
|
+
try {
|
|
244
|
+
const issue = await getProvider().getIssue(params.id, params.workspaceId);
|
|
245
|
+
if (!issue)
|
|
246
|
+
return textResult(`Issue not found: ${params.id}`);
|
|
247
|
+
return textResult(JSON.stringify(issue, null, 2));
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
104
253
|
}),
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
254
|
+
defineTool({
|
|
255
|
+
name: 'issue_create',
|
|
256
|
+
label: 'Teamwork',
|
|
257
|
+
description: 'Create a new issue (a work item / ticket) in the shared project tracker. Use this to file work for humans or other agents to pick up.',
|
|
258
|
+
promptSnippet: 'Create issues / tickets in a shared project tracker for humans or agents to collaborate on.',
|
|
259
|
+
parameters: Type.Object({
|
|
260
|
+
workspaceId: Type.Optional(Type.String({
|
|
261
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
262
|
+
})),
|
|
263
|
+
title: Type.String({ description: 'Issue title.' }),
|
|
264
|
+
description: Type.Optional(Type.String({ description: 'Issue description.' })),
|
|
265
|
+
priority: Type.Optional(Type.String({ description: 'Priority (e.g. low, medium, high, urgent).' })),
|
|
266
|
+
assignee: Type.Optional(Type.String({ description: 'Assignee name.' })),
|
|
267
|
+
project: Type.Optional(Type.String({ description: 'Project ID to associate with.' })),
|
|
268
|
+
}),
|
|
269
|
+
async execute(_toolCallId, params) {
|
|
270
|
+
const err = await ensureReady();
|
|
271
|
+
if (err)
|
|
272
|
+
return textResult(err);
|
|
273
|
+
try {
|
|
274
|
+
const issue = await getProvider().createIssue(params);
|
|
275
|
+
return textResult(JSON.stringify(issue, null, 2));
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
130
281
|
}),
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
282
|
+
defineTool({
|
|
283
|
+
name: 'issue_update',
|
|
284
|
+
label: 'Teamwork',
|
|
285
|
+
description: 'Update an existing issue in the shared project tracker. Can change title, description, status, priority, or assignee.',
|
|
286
|
+
parameters: Type.Object({
|
|
287
|
+
workspaceId: Type.Optional(Type.String({
|
|
288
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
289
|
+
})),
|
|
290
|
+
id: Type.String({ description: 'The issue ID to update.' }),
|
|
291
|
+
title: Type.Optional(Type.String({ description: 'New title.' })),
|
|
292
|
+
description: Type.Optional(Type.String({ description: 'New description.' })),
|
|
293
|
+
status: Type.Optional(Type.String({
|
|
294
|
+
description: 'New status (e.g. todo, in_progress, in_review, done, blocked, cancelled).',
|
|
295
|
+
})),
|
|
296
|
+
priority: Type.Optional(Type.String({ description: 'New priority.' })),
|
|
297
|
+
assignee: Type.Optional(Type.String({ description: 'New assignee name.' })),
|
|
298
|
+
}),
|
|
299
|
+
async execute(_toolCallId, params) {
|
|
300
|
+
const err = await ensureReady();
|
|
301
|
+
if (err)
|
|
302
|
+
return textResult(err);
|
|
303
|
+
const { id, workspaceId, ...input } = params;
|
|
304
|
+
try {
|
|
305
|
+
const issue = await getProvider().updateIssue(id, input, workspaceId);
|
|
306
|
+
if (!issue)
|
|
307
|
+
return textResult(`Issue not found: ${id}`);
|
|
308
|
+
return textResult(JSON.stringify(issue, null, 2));
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
312
|
+
}
|
|
313
|
+
},
|
|
154
314
|
}),
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return textResult(
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
315
|
+
defineTool({
|
|
316
|
+
name: 'issue_comment',
|
|
317
|
+
label: 'Teamwork',
|
|
318
|
+
description: 'Add a comment to an issue in the shared project tracker. Use for progress updates, questions, or blockers visible to other collaborators (humans or agents).',
|
|
319
|
+
parameters: Type.Object({
|
|
320
|
+
workspaceId: Type.Optional(Type.String({
|
|
321
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
322
|
+
})),
|
|
323
|
+
issueId: Type.String({ description: 'The issue ID to comment on.' }),
|
|
324
|
+
content: Type.String({ description: 'Comment content.' }),
|
|
325
|
+
parentId: Type.Optional(Type.String({
|
|
326
|
+
description: 'Optional provider-specific parent comment ID. The AMaster provider currently creates top-level comments.',
|
|
327
|
+
})),
|
|
328
|
+
}),
|
|
329
|
+
async execute(_toolCallId, params) {
|
|
330
|
+
const err = await ensureReady();
|
|
331
|
+
if (err)
|
|
332
|
+
return textResult(err);
|
|
333
|
+
try {
|
|
334
|
+
const comment = await getProvider().addComment(params.issueId, params.content, params.parentId, params.workspaceId);
|
|
335
|
+
return textResult(JSON.stringify(comment, null, 2));
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
}),
|
|
342
|
+
defineTool({
|
|
343
|
+
name: 'project_list',
|
|
344
|
+
label: 'Teamwork',
|
|
345
|
+
description: 'List all projects in a workspace.',
|
|
346
|
+
parameters: Type.Object({
|
|
347
|
+
workspaceId: Type.Optional(Type.String({
|
|
348
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
349
|
+
})),
|
|
350
|
+
}),
|
|
351
|
+
async execute(_toolCallId, params) {
|
|
352
|
+
const err = await ensureReady();
|
|
353
|
+
if (err)
|
|
354
|
+
return textResult(err);
|
|
355
|
+
try {
|
|
356
|
+
const projects = await getProvider().listProjects(params.workspaceId);
|
|
357
|
+
if (projects.length === 0)
|
|
358
|
+
return textResult('No projects found.');
|
|
359
|
+
return textResult(JSON.stringify(projects, null, 2));
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
}),
|
|
366
|
+
defineTool({
|
|
367
|
+
name: 'user_directory_list',
|
|
368
|
+
label: 'Teamwork',
|
|
369
|
+
description: 'List assignable AMaster users or members in the current teamwork workspace. This is read-only and helps choose assignees.',
|
|
370
|
+
parameters: Type.Object({
|
|
371
|
+
workspaceId: Type.Optional(Type.String({
|
|
372
|
+
description: 'Workspace ID. For AMaster provider this is the canonical companyId from workspace_list and is passed to the CLI as -C. Optional when the account has exactly one workspace.',
|
|
373
|
+
})),
|
|
374
|
+
q: Type.Optional(Type.String({ description: 'Optional search text.' })),
|
|
375
|
+
limit: Type.Optional(Type.Number({ description: 'Max number of results.' })),
|
|
376
|
+
}),
|
|
377
|
+
async execute(_toolCallId, params) {
|
|
378
|
+
const err = await ensureReady();
|
|
379
|
+
if (err)
|
|
380
|
+
return textResult(err);
|
|
381
|
+
const activeProvider = getProvider();
|
|
382
|
+
if (!activeProvider.listUserDirectory) {
|
|
383
|
+
return textResult('The current teamwork provider does not support user_directory_list.');
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const users = await activeProvider.listUserDirectory(params);
|
|
387
|
+
if (users.length === 0)
|
|
388
|
+
return textResult('No assignable users found.');
|
|
389
|
+
return textResult(JSON.stringify(users, null, 2));
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
defineTool({
|
|
397
|
+
name: 'teamwork_status',
|
|
398
|
+
label: 'Teamwork',
|
|
399
|
+
description: 'Check the status of the teamwork collaboration provider (daemon status, connected agents, etc.).',
|
|
400
|
+
parameters: Type.Object({}),
|
|
401
|
+
async execute() {
|
|
402
|
+
const err = await ensureReady();
|
|
403
|
+
if (err)
|
|
404
|
+
return textResult(err);
|
|
405
|
+
try {
|
|
406
|
+
const s = await getProvider().status();
|
|
407
|
+
return textResult(JSON.stringify(s, null, 2));
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
return textResult(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
}),
|
|
414
|
+
];
|
|
415
|
+
}
|
|
416
|
+
function supportedToolNamesForProvider(provider) {
|
|
417
|
+
const supportedToolNames = new Set(TEAMWORK_TOOL_ORDER);
|
|
418
|
+
if (!provider.listUserDirectory)
|
|
419
|
+
supportedToolNames.delete('user_directory_list');
|
|
420
|
+
return supportedToolNames;
|
|
421
|
+
}
|
|
422
|
+
function alignActiveTeamworkTools(pi, supportedToolNames, suspendedToolNames) {
|
|
423
|
+
try {
|
|
424
|
+
const activeToolNames = pi.getActiveTools();
|
|
425
|
+
const nextActiveToolNames = [];
|
|
426
|
+
for (const toolName of activeToolNames) {
|
|
427
|
+
if (!TEAMWORK_TOOL_NAMES.has(toolName) || supportedToolNames.has(toolName)) {
|
|
428
|
+
nextActiveToolNames.push(toolName);
|
|
429
|
+
if (supportedToolNames.has(toolName))
|
|
430
|
+
suspendedToolNames.delete(toolName);
|
|
431
|
+
continue;
|
|
197
432
|
}
|
|
198
|
-
|
|
199
|
-
|
|
433
|
+
suspendedToolNames.add(toolName);
|
|
434
|
+
}
|
|
435
|
+
for (const toolName of TEAMWORK_TOOL_ORDER) {
|
|
436
|
+
if (supportedToolNames.has(toolName) &&
|
|
437
|
+
suspendedToolNames.has(toolName) &&
|
|
438
|
+
!nextActiveToolNames.includes(toolName)) {
|
|
439
|
+
nextActiveToolNames.push(toolName);
|
|
440
|
+
suspendedToolNames.delete(toolName);
|
|
200
441
|
}
|
|
201
|
-
}
|
|
202
|
-
|
|
442
|
+
}
|
|
443
|
+
if (nextActiveToolNames.join('\0') !== activeToolNames.join('\0')) {
|
|
444
|
+
pi.setActiveTools(nextActiveToolNames);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// Older test harnesses and pre-session runtimes may not expose active tool controls.
|
|
449
|
+
}
|
|
203
450
|
}
|
|
204
451
|
function textResult(text) {
|
|
205
452
|
return { content: [{ type: 'text', text }], details: undefined };
|
|
206
453
|
}
|
|
454
|
+
function execWithEnv(command, args, env, cwd) {
|
|
455
|
+
return new Promise((resolve) => {
|
|
456
|
+
const child = spawn(command, args, {
|
|
457
|
+
cwd,
|
|
458
|
+
shell: false,
|
|
459
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
460
|
+
env: { ...process.env, ...env },
|
|
461
|
+
});
|
|
462
|
+
let stdout = '';
|
|
463
|
+
let stderr = '';
|
|
464
|
+
let settled = false;
|
|
465
|
+
const finish = (result) => {
|
|
466
|
+
if (settled)
|
|
467
|
+
return;
|
|
468
|
+
settled = true;
|
|
469
|
+
resolve(result);
|
|
470
|
+
};
|
|
471
|
+
child.stdout?.on('data', (chunk) => {
|
|
472
|
+
stdout += String(chunk);
|
|
473
|
+
});
|
|
474
|
+
child.stderr?.on('data', (chunk) => {
|
|
475
|
+
stderr += String(chunk);
|
|
476
|
+
});
|
|
477
|
+
child.on('error', (error) => {
|
|
478
|
+
finish({ stdout, stderr: error.message, code: 1 });
|
|
479
|
+
});
|
|
480
|
+
child.on('close', (code) => {
|
|
481
|
+
finish({ stdout, stderr, code: code ?? 0 });
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
}
|
|
207
485
|
function loadConfig(cwd) {
|
|
208
486
|
try {
|
|
209
487
|
const config = loadPiSettings(SETTINGS_KEY, {
|
|
210
488
|
cwd,
|
|
211
|
-
agentDir: resolveAgentDir(),
|
|
212
489
|
});
|
|
213
490
|
return Object.keys(config).length > 0 ? config : {};
|
|
214
491
|
}
|