ftown-bridge 0.19.10 → 0.19.12
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
package/pi-extension/ftown.js
CHANGED
|
@@ -361,14 +361,35 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
361
361
|
});
|
|
362
362
|
|
|
363
363
|
async function runSessions(params) {
|
|
364
|
-
requireOperation(params, ['list', 'archive', 'get', 'usage', 'running', 'screen', 'grep']);
|
|
365
|
-
if (!['list', 'archive'].includes(params.operation)) requireString(params, 'target');
|
|
364
|
+
requireOperation(params, ['list', 'children', 'archive', 'get', 'usage', 'running', 'screen', 'grep']);
|
|
365
|
+
if (!['list', 'children', 'archive'].includes(params.operation)) requireString(params, 'target');
|
|
366
366
|
if (params.operation === 'grep') requireString(params, 'pattern');
|
|
367
367
|
if (params.operation === 'archive') {
|
|
368
368
|
return requestJson('/api/archive', { method: 'GET' });
|
|
369
369
|
}
|
|
370
370
|
const sessions = await listSessions();
|
|
371
|
-
|
|
371
|
+
const filterByPattern = (candidates) => {
|
|
372
|
+
if (!params.pattern) return candidates;
|
|
373
|
+
let pattern;
|
|
374
|
+
try {
|
|
375
|
+
pattern = new RegExp(params.pattern, 'i');
|
|
376
|
+
} catch (error) {
|
|
377
|
+
throw new Error(`Invalid session pattern: ${error instanceof Error ? error.message : String(error)}`);
|
|
378
|
+
}
|
|
379
|
+
return candidates.filter((session) =>
|
|
380
|
+
Object.values(session).some((value) => typeof value === 'string' && pattern.test(value)));
|
|
381
|
+
};
|
|
382
|
+
if (params.operation === 'children') {
|
|
383
|
+
const parentSessionId = params.target
|
|
384
|
+
? resolveSession(sessions, params.target).id
|
|
385
|
+
: ftownSessionId;
|
|
386
|
+
if (!parentSessionId) throw new Error('children requires target outside an ftown session');
|
|
387
|
+
const children = sessions.filter((session) => session.parentSessionId === parentSessionId);
|
|
388
|
+
return { sessions: filterByPattern(children) };
|
|
389
|
+
}
|
|
390
|
+
if (params.operation === 'list') {
|
|
391
|
+
return { sessions: filterByPattern(sessions) };
|
|
392
|
+
}
|
|
372
393
|
const session = resolveSession(sessions, params.target);
|
|
373
394
|
if (params.operation === 'get') {
|
|
374
395
|
return requestJson(`/api/sessions/${encodeURIComponent(session.id)}`, { method: 'GET' });
|
|
@@ -400,7 +421,10 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
400
421
|
});
|
|
401
422
|
}
|
|
402
423
|
|
|
403
|
-
const targetProperty = {
|
|
424
|
+
const targetProperty = {
|
|
425
|
+
type: 'string',
|
|
426
|
+
description: 'Session id or unique exact name; optional parent for children (defaults to current session).',
|
|
427
|
+
};
|
|
404
428
|
const pageProperties = {
|
|
405
429
|
offset: { type: 'integer', minimum: 0 },
|
|
406
430
|
limit: { type: 'integer', minimum: 1, maximum: 1000 },
|
|
@@ -409,16 +433,19 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
409
433
|
pi.registerTool({
|
|
410
434
|
name: 'ftown_sessions',
|
|
411
435
|
label: 'ftown sessions',
|
|
412
|
-
description: 'List or inspect ftown sessions, running state, archive, token usage, terminal screen, and terminal log matches.',
|
|
436
|
+
description: 'List, filter, or inspect ftown sessions, including children, running state, archive, token usage, terminal screen, and terminal log matches.',
|
|
413
437
|
parameters: {
|
|
414
438
|
type: 'object',
|
|
415
439
|
properties: {
|
|
416
440
|
operation: {
|
|
417
441
|
type: 'string',
|
|
418
|
-
enum: ['list', 'archive', 'get', 'usage', 'running', 'screen', 'grep'],
|
|
442
|
+
enum: ['list', 'children', 'archive', 'get', 'usage', 'running', 'screen', 'grep'],
|
|
419
443
|
},
|
|
420
444
|
target: targetProperty,
|
|
421
|
-
pattern: {
|
|
445
|
+
pattern: {
|
|
446
|
+
type: 'string', minLength: 1,
|
|
447
|
+
description: 'Case-insensitive regex for list/children filtering; required for terminal grep.',
|
|
448
|
+
},
|
|
422
449
|
...pageProperties,
|
|
423
450
|
context: { type: 'integer', minimum: 0, maximum: 10 },
|
|
424
451
|
},
|
package/skills/factory/SKILL.md
CHANGED
|
@@ -34,15 +34,21 @@ already initialized (point them at `status`/`up`/`down` instead) — do not over
|
|
|
34
34
|
`uv` is on PATH.
|
|
35
35
|
2. **Install fticket.** `uv tool install fticket` (or `uv tool upgrade fticket` if already
|
|
36
36
|
installed). Verify with `fts --help`.
|
|
37
|
-
3. **
|
|
37
|
+
3. **Resolve routing and copy the template.** If the deployment prompt names the
|
|
38
|
+
initiating harness/model, use those values. Otherwise, when `FTOWN_SESSION_ID` is
|
|
39
|
+
set, run `~/.ftown/ftown-sessions get "$FTOWN_SESSION_ID"` and read this session's
|
|
40
|
+
`shellType` and optional `model`. If neither source identifies the harness, ask the
|
|
41
|
+
user instead of assuming one.
|
|
38
42
|
```bash
|
|
39
43
|
cp -r ~/.ftown/skills/factory/factory-template "$REPO/factory"
|
|
40
44
|
```
|
|
41
|
-
`factory/bin` moves in as-is (no edits).
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
the
|
|
45
|
-
|
|
45
|
+
`factory/bin` moves in as-is (no edits). Set every stage plus `triage` and `digest` to
|
|
46
|
+
the initiating harness/model. When the initiator has no explicit model, remove each
|
|
47
|
+
template `model` value so the harness default is used. Only retain or add a different
|
|
48
|
+
route when the user explicitly requested that override. Also apply the project name
|
|
49
|
+
(default: the repo directory's basename), operator ftown session id (optional —
|
|
50
|
+
default `-`, no operator mail), and shared resources (default `staging`,
|
|
51
|
+
exclusive-only). Edit `factory/factory.yaml` with these values.
|
|
46
52
|
4. **Gitignore + state dirs.** Append `.ffactory/` to `$REPO/.gitignore` (create the file
|
|
47
53
|
if absent). `mkdir -p "$REPO/.ffactory/tickets"`.
|
|
48
54
|
5. **Init the ticket db**, reading stage order and limits back out of the edited
|
|
@@ -272,7 +272,11 @@ def capacity_spawn(
|
|
|
272
272
|
db_path: Path,
|
|
273
273
|
dry_run: bool,
|
|
274
274
|
) -> tuple[list[int], int, int, int]:
|
|
275
|
-
"""Claim + spawn up to capacity, stages
|
|
275
|
+
"""Claim + spawn up to capacity, later stages first (pipeline drain).
|
|
276
|
+
|
|
277
|
+
Reversing yaml stage order prefers tickets already moving through the pipeline over
|
|
278
|
+
admitting new work at the first stage. With max_sessions=1, one ticket therefore
|
|
279
|
+
runs end-to-end before the dispatcher starts the next one.
|
|
276
280
|
|
|
277
281
|
Returns (spawned_ids, rejected_input, expiry_capped, active_total). Guards run just
|
|
278
282
|
after the claim (fts.claim picks the ticket, so preflight can't run earlier); a guard
|
|
@@ -297,7 +301,7 @@ def capacity_spawn(
|
|
|
297
301
|
except FTSError as exc:
|
|
298
302
|
print(f"dead_letter failed for ticket {ticket_id}: {exc}", file=sys.stderr)
|
|
299
303
|
|
|
300
|
-
for stage in cfg.stages:
|
|
304
|
+
for stage in reversed(cfg.stages):
|
|
301
305
|
free = min(stage.max_workers - active_of(stage.name), budget)
|
|
302
306
|
if free <= 0:
|
|
303
307
|
continue
|
|
@@ -5,7 +5,7 @@ project: my-project # used to namespace ftown loops and worker names
|
|
|
5
5
|
operator_session: "-" # ftown session id for digests/worker mail; "-" = none
|
|
6
6
|
|
|
7
7
|
limits:
|
|
8
|
-
max_sessions: 6 # global
|
|
8
|
+
max_sessions: 6 # global cap; set 1 for strict one-ticket end-to-end flow
|
|
9
9
|
bounce_limit: 3 # rejections before auto dead-letter (fts config)
|
|
10
10
|
claim_ttl_ms: 1800000 # default claim TTL (30m); stages may override
|
|
11
11
|
max_ticket_age_h: 48 # dispatcher dead-letters tickets older than this
|
|
@@ -17,6 +17,7 @@ resources:
|
|
|
17
17
|
- name: staging
|
|
18
18
|
policy: exclusive_only # both | exclusive_only | shared_only
|
|
19
19
|
|
|
20
|
+
# Dispatcher claims later stages first, draining work before admitting new tickets.
|
|
20
21
|
# Harness per stage: claude | codex | cursor | zai | kimi | deepseek | fireworks
|
|
21
22
|
# next: where advance() sends the ticket on success (- = terminal)
|
|
22
23
|
# bounce: where reject() sends it (- = rejection not allowed)
|