@forloop-cc/forloop-opencode-plugin-planner 2.2.25 → 2.2.26
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@forloop-cc/forloop-opencode-plugin-planner",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.26",
|
|
5
5
|
"description": "ForLoop integration plugin for opencode — Sprint, story, and agent tools for the ForLoop platform",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -190,7 +190,7 @@ export function createCoreServiceGetTool(client: ForLoopAPIClient) {
|
|
|
190
190
|
export function createCoreServiceIntegrationRequestTool(client: ForLoopAPIClient) {
|
|
191
191
|
return tool({
|
|
192
192
|
description:
|
|
193
|
-
'
|
|
193
|
+
'Start the user-approval step for a core-service integration: persist (or idempotently reuse) the pending connection request, then ask the signed-in user’s live client to open App Connections focused on it so they only need to click Approve. Falls back to the approval link when the client is offline. This never approves, never touches credentials, and never constructs API URLs by hand.',
|
|
194
194
|
args: {
|
|
195
195
|
agentId: tool.schema.string().describe('Target core-service agentId'),
|
|
196
196
|
scopes: tool.schema
|
|
@@ -262,43 +262,86 @@ export function createCoreServiceIntegrationRequestTool(client: ForLoopAPIClient
|
|
|
262
262
|
linkParams.set('connect', args.agentId);
|
|
263
263
|
linkParams.set('scopes', requested.join(','));
|
|
264
264
|
linkParams.set('subject', 'app');
|
|
265
|
-
const
|
|
265
|
+
const fallbackConnectUrl = spaceUrl ? `${spaceUrl}?${linkParams.toString()}` : null;
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
// Persist the pending request and nudge the live client to open the
|
|
268
|
+
// panel. Both steps are best-effort: an offline client (or a backend
|
|
269
|
+
// without the request routes) must still return an actionable link.
|
|
270
|
+
let requestId: string | null = null;
|
|
271
|
+
let delivered = false;
|
|
272
|
+
let connectUrl: string | null = fallbackConnectUrl;
|
|
273
|
+
let dropped: string[] = [];
|
|
274
|
+
if (callerSprintId) {
|
|
275
|
+
try {
|
|
276
|
+
const created = await client.createConnectionRequest({
|
|
277
|
+
sprintId: callerSprintId,
|
|
278
|
+
targetAgentId: args.agentId,
|
|
279
|
+
scopes: requested,
|
|
280
|
+
subjectType: 'app',
|
|
281
|
+
requestedBy: 'planner',
|
|
282
|
+
reason: args.reason,
|
|
283
|
+
});
|
|
284
|
+
const request = created?.request ?? {};
|
|
285
|
+
requestId = typeof request.id === 'string' ? request.id : null;
|
|
286
|
+
dropped = Array.isArray(created?.droppedScopes) ? created.droppedScopes : [];
|
|
287
|
+
connectUrl = created?.link ?? request.link ?? connectUrl;
|
|
288
|
+
|
|
289
|
+
if (requestId) {
|
|
290
|
+
try {
|
|
291
|
+
const opened = await client.openConnectionRequest(requestId);
|
|
292
|
+
delivered = opened?.delivered === true;
|
|
293
|
+
connectUrl = opened?.link ?? opened?.fallback ?? connectUrl;
|
|
294
|
+
} catch {
|
|
295
|
+
delivered = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
// Older backend without request persistence: fall back to the link.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const manualSteps = connectUrl
|
|
268
304
|
? [
|
|
269
305
|
`1. Open ${connectUrl}`,
|
|
270
306
|
` This opens **${target.name}**'s connection panel in this app, with the scopes below and \`This app\` preselected.`,
|
|
271
307
|
'2. Review the service, scopes and price shown above.',
|
|
272
|
-
'3. Click **
|
|
273
|
-
'4. Copy the **Connection ID** shown under the new connected app into the integration brief.',
|
|
308
|
+
'3. Click **Approve**.',
|
|
274
309
|
]
|
|
275
310
|
: [
|
|
276
311
|
`1. Open the app's space${spaceUrl ? `: ${spaceUrl}` : ''}.`,
|
|
277
312
|
'2. Open **Aivy Actions → App Connections (Beta)** and choose the **Connect** tab.',
|
|
278
|
-
`3. Find **${target.name}**, tick the scopes below, choose **This app**, then click **
|
|
279
|
-
'4. Copy the **Connection ID** shown under the new connected app into the integration brief.',
|
|
313
|
+
`3. Find **${target.name}**, tick the scopes below, choose **This app**, then click **Approve**.`,
|
|
280
314
|
];
|
|
281
315
|
|
|
282
316
|
return [
|
|
283
|
-
|
|
317
|
+
delivered
|
|
318
|
+
? '## App Connections opened — waiting for the app owner to approve'
|
|
319
|
+
: '## Approval needed — the app owner must approve this request',
|
|
284
320
|
`- Service: **${target.name}** (\`${target.agentId}\`)`,
|
|
285
321
|
`- serviceCode: ${target.serviceCode ?? 'unknown'}`,
|
|
286
322
|
`- reason: ${args.reason}`,
|
|
287
323
|
`- scopes: ${requested.join(', ')}`,
|
|
288
324
|
`- cost: ${formatPrice(target)}`,
|
|
325
|
+
...(requestId ? [`- requestId: \`${requestId}\``] : []),
|
|
326
|
+
...(dropped.length
|
|
327
|
+
? [`⚠️ Dropped (not declared by the target manifest): ${dropped.join(', ')}`]
|
|
328
|
+
: []),
|
|
289
329
|
'',
|
|
290
|
-
|
|
330
|
+
delivered
|
|
331
|
+
? 'The App Connections panel is now open on the user’s screen, focused on this request. They only need to click **Approve** — do not paste a link or repeat manual steps.'
|
|
332
|
+
: 'The user’s client was not reachable. Send them this link — it opens the same focused panel so they only need to click **Approve**:',
|
|
333
|
+
...(delivered ? [] : ['', ...manualSteps]),
|
|
291
334
|
'',
|
|
292
|
-
'
|
|
293
|
-
|
|
335
|
+
'A pending request is not a grant. Do not write a connection id into the integration brief until `a2a_check_connections` reports an active connection.',
|
|
336
|
+
'After the user approves, call `a2a_check_connections` to read the real connection id and scopes.',
|
|
294
337
|
'',
|
|
295
338
|
'### Connection payload (owner handoff)',
|
|
296
339
|
'```json',
|
|
297
340
|
JSON.stringify(connectionPayload, null, 2),
|
|
298
341
|
'```',
|
|
299
342
|
'',
|
|
300
|
-
'
|
|
301
|
-
'If the user cannot or does not complete the approval now, write `Connection:
|
|
343
|
+
'The planner can never approve. Never send credentials or create the connection from the planner.',
|
|
344
|
+
'If the user cannot or does not complete the approval now, write `Connection: pending owner approval` in the integration brief and continue — do not block the plan and do not ask again.',
|
|
302
345
|
].join('\n');
|
|
303
346
|
} catch (error: any) {
|
|
304
347
|
return `❌ Error creating integration request: ${error.message}`;
|