ftown-bridge 0.19.8 → 0.19.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/package.json +1 -1
- package/pi-extension/ftown.js +119 -117
package/package.json
CHANGED
package/pi-extension/ftown.js
CHANGED
|
@@ -211,7 +211,36 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
211
211
|
throw new Error(`Session not found: ${normalized}`);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
function hasOwn(value, key) {
|
|
215
|
+
return value !== null && typeof value === 'object'
|
|
216
|
+
&& Object.prototype.hasOwnProperty.call(value, key);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function requireString(params, field, label = field) {
|
|
220
|
+
const value = params?.[field];
|
|
221
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} is required`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function requireProperty(params, field, label = field) {
|
|
225
|
+
if (!hasOwn(params, field)) throw new Error(`${label} is required`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function requireOperation(params, allowed) {
|
|
229
|
+
if (!allowed.includes(params?.operation)) {
|
|
230
|
+
throw new Error(`operation must be one of: ${allowed.join(', ')}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function validateMailParams(params) {
|
|
235
|
+
requireOperation(params, ['send', 'read']);
|
|
236
|
+
if (params.operation === 'send') {
|
|
237
|
+
requireString(params, 'target');
|
|
238
|
+
requireString(params, 'body');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
214
242
|
async function runMail(params) {
|
|
243
|
+
validateMailParams(params);
|
|
215
244
|
if (params.operation === 'read') {
|
|
216
245
|
if (!ftownSessionId) throw new Error('FTOWN_SESSION_ID is unavailable');
|
|
217
246
|
const query = new URLSearchParams({ wait: '0' });
|
|
@@ -261,31 +290,19 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
261
290
|
label: 'ftown mail',
|
|
262
291
|
description: 'Send durable mail to another ftown session, or read this session inbox.',
|
|
263
292
|
parameters: {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
{
|
|
278
|
-
type: 'object',
|
|
279
|
-
properties: {
|
|
280
|
-
operation: { const: 'read' },
|
|
281
|
-
peek: { type: 'boolean', description: 'Do not mark messages delivered.' },
|
|
282
|
-
all: { type: 'boolean', description: 'Include already-delivered messages.' },
|
|
283
|
-
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
284
|
-
},
|
|
285
|
-
required: ['operation'],
|
|
286
|
-
additionalProperties: false,
|
|
287
|
-
},
|
|
288
|
-
],
|
|
293
|
+
type: 'object',
|
|
294
|
+
properties: {
|
|
295
|
+
operation: { type: 'string', enum: ['send', 'read'] },
|
|
296
|
+
target: { type: 'string', description: 'Session id/name, or "parent".' },
|
|
297
|
+
body: { type: 'string', minLength: 1 },
|
|
298
|
+
type: { type: 'string', enum: ['message', 'task', 'result', 'escalation'] },
|
|
299
|
+
threadId: { type: 'string' },
|
|
300
|
+
peek: { type: 'boolean', description: 'Do not mark messages delivered.' },
|
|
301
|
+
all: { type: 'boolean', description: 'Include already-delivered messages.' },
|
|
302
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
303
|
+
},
|
|
304
|
+
required: ['operation'],
|
|
305
|
+
additionalProperties: false,
|
|
289
306
|
},
|
|
290
307
|
async execute(toolCallId, params) {
|
|
291
308
|
try {
|
|
@@ -322,6 +339,9 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
322
339
|
});
|
|
323
340
|
|
|
324
341
|
async function runSessions(params) {
|
|
342
|
+
requireOperation(params, ['list', 'archive', 'get', 'usage', 'running', 'screen', 'grep']);
|
|
343
|
+
if (!['list', 'archive'].includes(params.operation)) requireString(params, 'target');
|
|
344
|
+
if (params.operation === 'grep') requireString(params, 'pattern');
|
|
325
345
|
if (params.operation === 'archive') {
|
|
326
346
|
return requestJson('/api/archive', { method: 'GET' });
|
|
327
347
|
}
|
|
@@ -369,30 +389,19 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
369
389
|
label: 'ftown sessions',
|
|
370
390
|
description: 'List or inspect ftown sessions, running state, archive, token usage, terminal screen, and terminal log matches.',
|
|
371
391
|
parameters: {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
...['get', 'usage', 'running'].map((operation) => ({
|
|
378
|
-
type: 'object', properties: { operation: { const: operation }, target: targetProperty },
|
|
379
|
-
required: ['operation', 'target'], additionalProperties: false,
|
|
380
|
-
})),
|
|
381
|
-
{
|
|
382
|
-
type: 'object',
|
|
383
|
-
properties: { operation: { const: 'screen' }, target: targetProperty, ...pageProperties },
|
|
384
|
-
required: ['operation', 'target'], additionalProperties: false,
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
type: 'object',
|
|
388
|
-
properties: {
|
|
389
|
-
operation: { const: 'grep' }, target: targetProperty,
|
|
390
|
-
pattern: { type: 'string', minLength: 1 }, ...pageProperties,
|
|
391
|
-
context: { type: 'integer', minimum: 0, maximum: 10 },
|
|
392
|
-
},
|
|
393
|
-
required: ['operation', 'target', 'pattern'], additionalProperties: false,
|
|
392
|
+
type: 'object',
|
|
393
|
+
properties: {
|
|
394
|
+
operation: {
|
|
395
|
+
type: 'string',
|
|
396
|
+
enum: ['list', 'archive', 'get', 'usage', 'running', 'screen', 'grep'],
|
|
394
397
|
},
|
|
395
|
-
|
|
398
|
+
target: targetProperty,
|
|
399
|
+
pattern: { type: 'string', minLength: 1 },
|
|
400
|
+
...pageProperties,
|
|
401
|
+
context: { type: 'integer', minimum: 0, maximum: 10 },
|
|
402
|
+
},
|
|
403
|
+
required: ['operation'],
|
|
404
|
+
additionalProperties: false,
|
|
396
405
|
},
|
|
397
406
|
async execute(_toolCallId, params) {
|
|
398
407
|
try {
|
|
@@ -422,6 +431,7 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
422
431
|
type: 'object',
|
|
423
432
|
properties: {
|
|
424
433
|
shell: {
|
|
434
|
+
type: 'string',
|
|
425
435
|
enum: ['claude', 'cursor', 'codex', 'grok', 'pi', 'kimi-code', 'opencode', 'shell', 'zai', 'kimi', 'deepseek', 'fireworks'],
|
|
426
436
|
},
|
|
427
437
|
prompt: { type: 'string', minLength: 1 },
|
|
@@ -467,6 +477,10 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
467
477
|
});
|
|
468
478
|
|
|
469
479
|
async function runSessionManage(params) {
|
|
480
|
+
requireOperation(params, ['stop', 'remove', 'revive', 'rename', 'reparent']);
|
|
481
|
+
requireString(params, 'target');
|
|
482
|
+
if (params.operation === 'rename') requireString(params, 'name');
|
|
483
|
+
if (params.operation === 'reparent') requireProperty(params, 'parent');
|
|
470
484
|
if (params.operation === 'revive') {
|
|
471
485
|
const payload = await requestJson('/api/archive', { method: 'GET' });
|
|
472
486
|
const archived = Array.isArray(payload?.archived) ? payload.archived : [];
|
|
@@ -517,28 +531,17 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
517
531
|
label: 'manage ftown session',
|
|
518
532
|
description: 'Stop, rename, reparent, remove, or revive an ftown session.',
|
|
519
533
|
parameters: {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
})),
|
|
525
|
-
{
|
|
526
|
-
type: 'object',
|
|
527
|
-
properties: {
|
|
528
|
-
operation: { const: 'rename' }, target: targetProperty,
|
|
529
|
-
name: { type: 'string', minLength: 1 },
|
|
530
|
-
},
|
|
531
|
-
required: ['operation', 'target', 'name'], additionalProperties: false,
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
type: 'object',
|
|
535
|
-
properties: {
|
|
536
|
-
operation: { const: 'reparent' }, target: targetProperty,
|
|
537
|
-
parent: { type: ['string', 'null'], description: 'Parent id/name; null clears it.' },
|
|
538
|
-
},
|
|
539
|
-
required: ['operation', 'target', 'parent'], additionalProperties: false,
|
|
534
|
+
type: 'object',
|
|
535
|
+
properties: {
|
|
536
|
+
operation: {
|
|
537
|
+
type: 'string', enum: ['stop', 'remove', 'revive', 'rename', 'reparent'],
|
|
540
538
|
},
|
|
541
|
-
|
|
539
|
+
target: targetProperty,
|
|
540
|
+
name: { type: 'string', minLength: 1 },
|
|
541
|
+
parent: { type: ['string', 'null'], description: 'Parent id/name; null clears it.' },
|
|
542
|
+
},
|
|
543
|
+
required: ['operation', 'target'],
|
|
544
|
+
additionalProperties: false,
|
|
542
545
|
},
|
|
543
546
|
async execute(toolCallId, params) {
|
|
544
547
|
try {
|
|
@@ -567,41 +570,54 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
567
570
|
}
|
|
568
571
|
|
|
569
572
|
const loopScheduleProperty = {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
type: 'object',
|
|
582
|
-
properties: {
|
|
583
|
-
kind: { const: 'cron' },
|
|
584
|
-
expression: { type: 'string', minLength: 1 },
|
|
585
|
-
tz: { type: 'string', minLength: 1 },
|
|
586
|
-
},
|
|
587
|
-
required: ['kind', 'expression'],
|
|
588
|
-
additionalProperties: false,
|
|
589
|
-
},
|
|
590
|
-
],
|
|
573
|
+
type: 'object',
|
|
574
|
+
properties: {
|
|
575
|
+
kind: { type: 'string', enum: ['interval', 'cron'] },
|
|
576
|
+
everyMs: { type: 'integer', minimum: 1000 },
|
|
577
|
+
expression: { type: 'string', minLength: 1 },
|
|
578
|
+
tz: { type: 'string', minLength: 1 },
|
|
579
|
+
},
|
|
580
|
+
required: ['kind'],
|
|
581
|
+
additionalProperties: false,
|
|
591
582
|
};
|
|
592
583
|
const loopDraftProperties = {
|
|
593
584
|
name: { type: 'string', minLength: 1 },
|
|
594
585
|
task: { type: 'string', minLength: 1 },
|
|
595
586
|
schedule: loopScheduleProperty,
|
|
596
|
-
shell: { enum: ['claude', 'cursor', 'codex', 'grok', 'pi', 'kimi-code', 'opencode', 'shell'] },
|
|
587
|
+
shell: { type: 'string', enum: ['claude', 'cursor', 'codex', 'grok', 'pi', 'kimi-code', 'opencode', 'shell'] },
|
|
597
588
|
workdir: { type: 'string' },
|
|
598
589
|
model: { type: 'string' },
|
|
599
590
|
enabled: { type: 'boolean' },
|
|
600
|
-
overlapPolicy: { enum: ['skip', 'allow'] },
|
|
591
|
+
overlapPolicy: { type: 'string', enum: ['skip', 'allow'] },
|
|
601
592
|
retention: { type: ['integer', 'null'], minimum: 0 },
|
|
602
593
|
maxRuntimeMs: { type: 'integer', minimum: 1000 },
|
|
603
594
|
group: { type: 'string' },
|
|
604
595
|
};
|
|
596
|
+
const loopDraftFields = Object.keys(loopDraftProperties);
|
|
597
|
+
|
|
598
|
+
function validateLoopSchedule(schedule) {
|
|
599
|
+
requireProperty({ schedule }, 'schedule');
|
|
600
|
+
if (schedule === null || typeof schedule !== 'object') throw new Error('schedule is required');
|
|
601
|
+
requireOperation({ operation: schedule.kind }, ['interval', 'cron']);
|
|
602
|
+
if (schedule.kind === 'interval') requireProperty(schedule, 'everyMs', 'schedule.everyMs');
|
|
603
|
+
if (schedule.kind === 'cron') requireString(schedule, 'expression', 'schedule.expression');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function validateLoopParams(params) {
|
|
607
|
+
requireOperation(params, ['list', 'get', 'runs', 'run_now', 'delete', 'create', 'update']);
|
|
608
|
+
if (['get', 'runs', 'run_now', 'delete', 'update'].includes(params.operation)) {
|
|
609
|
+
requireString(params, 'target');
|
|
610
|
+
}
|
|
611
|
+
if (params.operation === 'create') {
|
|
612
|
+
requireString(params, 'name');
|
|
613
|
+
requireString(params, 'task');
|
|
614
|
+
requireProperty(params, 'schedule');
|
|
615
|
+
}
|
|
616
|
+
if (params.operation === 'update' && !loopDraftFields.some((field) => hasOwn(params, field))) {
|
|
617
|
+
throw new Error('At least one field to update is required');
|
|
618
|
+
}
|
|
619
|
+
if (hasOwn(params, 'schedule')) validateLoopSchedule(params.schedule);
|
|
620
|
+
}
|
|
605
621
|
|
|
606
622
|
function loopBody(params, create = false) {
|
|
607
623
|
const body = {};
|
|
@@ -626,35 +642,21 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
626
642
|
label: 'ftown loops',
|
|
627
643
|
description: 'List, inspect, create, update, delete, or run scheduled ftown loops.',
|
|
628
644
|
parameters: {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
...['get', 'runs', 'run_now', 'delete'].map((operation) => ({
|
|
635
|
-
type: 'object',
|
|
636
|
-
properties: { operation: { const: operation }, target: { type: 'string' } },
|
|
637
|
-
required: ['operation', 'target'], additionalProperties: false,
|
|
638
|
-
})),
|
|
639
|
-
{
|
|
640
|
-
type: 'object',
|
|
641
|
-
properties: { operation: { const: 'create' }, ...loopDraftProperties },
|
|
642
|
-
required: ['operation', 'name', 'task', 'schedule'],
|
|
643
|
-
additionalProperties: false,
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
type: 'object',
|
|
647
|
-
properties: {
|
|
648
|
-
operation: { const: 'update' }, target: { type: 'string' }, ...loopDraftProperties,
|
|
649
|
-
},
|
|
650
|
-
required: ['operation', 'target'],
|
|
651
|
-
minProperties: 3,
|
|
652
|
-
additionalProperties: false,
|
|
645
|
+
type: 'object',
|
|
646
|
+
properties: {
|
|
647
|
+
operation: {
|
|
648
|
+
type: 'string',
|
|
649
|
+
enum: ['list', 'get', 'runs', 'run_now', 'delete', 'create', 'update'],
|
|
653
650
|
},
|
|
654
|
-
|
|
651
|
+
target: { type: 'string' },
|
|
652
|
+
...loopDraftProperties,
|
|
653
|
+
},
|
|
654
|
+
required: ['operation'],
|
|
655
|
+
additionalProperties: false,
|
|
655
656
|
},
|
|
656
657
|
async execute(toolCallId, params) {
|
|
657
658
|
try {
|
|
659
|
+
validateLoopParams(params);
|
|
658
660
|
if (params.operation === 'create') {
|
|
659
661
|
return await executeOnce('ftown_loops', toolCallId, async () =>
|
|
660
662
|
toolResult(await requestJson('/api/loops', {
|