@nac3/forge-cli 1.0.47 → 1.0.49
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/chat/server.d.ts +13 -0
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +91 -5
- package/dist/chat/server.js.map +1 -1
- package/dist/support/reports.d.ts +6 -0
- package/dist/support/reports.d.ts.map +1 -1
- package/dist/support/reports.js +26 -0
- package/dist/support/reports.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/chat/server.d.ts
CHANGED
|
@@ -74,6 +74,19 @@ interface Ctx {
|
|
|
74
74
|
setBrainProvider?: (mode: 'plan' | 'console') => Promise<boolean>;
|
|
75
75
|
bridge?: import('./bridge.js').BridgeHandle | null;
|
|
76
76
|
}
|
|
77
|
+
/** Pure ownership decision for a mobile chat command across N desktop
|
|
78
|
+
* instances (Pablo 2026-06-23). Exactly one instance must run each command.
|
|
79
|
+
* Run when: no slug (legacy mobile -> old broadcast); the slug is MY active
|
|
80
|
+
* project; I am alone / no bridge; or it is an orphan project (no live peer
|
|
81
|
+
* owns it) and I am the lowest-id instance. Skip when a live peer owns it. */
|
|
82
|
+
export declare function ownsMobileCommand(opts: {
|
|
83
|
+
projectSlug?: string;
|
|
84
|
+
activeSlug: string | null;
|
|
85
|
+
peerSlugs: Set<string>;
|
|
86
|
+
liveIds: string[];
|
|
87
|
+
selfId: string;
|
|
88
|
+
hasBridge: boolean;
|
|
89
|
+
}): boolean;
|
|
77
90
|
export declare function buildSystemPrompt(ctx: Ctx, mode?: ForgeMode, pilotState?: {
|
|
78
91
|
pilot_completed: boolean;
|
|
79
92
|
target_pending: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/chat/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAKjG,OAAO,EAAoD,KAAK,aAAa,EAAoB,MAAM,aAAa,CAAC;AAErH,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAEL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAczB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAsBjD,OAAO,EAAyD,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAc1H,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;;;;;wDAQoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,iBAAiB,CAAC;IACnD;;2DAEuD;IACvD,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;+CAC2C;IAC3C,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;kEAG8D;IAC9D,OAAO,CAAC,EAAE,OAAO,sBAAsB,EAAE,OAAO,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ;qEACiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAqBD,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/chat/server.ts"],"names":[],"mappings":"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"}
|
package/dist/chat/server.js
CHANGED
|
@@ -440,7 +440,15 @@ export async function startChatServer(opts) {
|
|
|
440
440
|
* restart needed). When the relay is off it is a cheap no-op timer. */
|
|
441
441
|
let commandConsumerHandle = null;
|
|
442
442
|
try {
|
|
443
|
-
commandConsumerHandle = startDesktopCommandConsumer(opts.port
|
|
443
|
+
commandConsumerHandle = startDesktopCommandConsumer(opts.port, {
|
|
444
|
+
/* live so a reanchor moves which commands this instance owns */
|
|
445
|
+
getActiveSlug: () => session.projectSlug,
|
|
446
|
+
/* bridgeHandle is assigned just below; the closure reads it lazily at
|
|
447
|
+
* refresh time (after boot), so multi-instance ownership works without
|
|
448
|
+
* reordering. Null bridge -> single-instance assumption (run all). */
|
|
449
|
+
getBridgeState: async () => (bridgeHandle ? bridgeHandle.state() : null),
|
|
450
|
+
getSelfId: () => bridgeHandle?.selfId ?? '',
|
|
451
|
+
});
|
|
444
452
|
}
|
|
445
453
|
catch {
|
|
446
454
|
commandConsumerHandle = null;
|
|
@@ -575,8 +583,68 @@ function getRelayPublisher() {
|
|
|
575
583
|
* paired phone's chat commands, runs each through THIS server's own
|
|
576
584
|
* /api/chat (loopback, so the full BYOK brain + project context apply),
|
|
577
585
|
* and publishes the reply back as chat_reply. Gated YF_RELAY=1. */
|
|
578
|
-
|
|
586
|
+
/** Pure ownership decision for a mobile chat command across N desktop
|
|
587
|
+
* instances (Pablo 2026-06-23). Exactly one instance must run each command.
|
|
588
|
+
* Run when: no slug (legacy mobile -> old broadcast); the slug is MY active
|
|
589
|
+
* project; I am alone / no bridge; or it is an orphan project (no live peer
|
|
590
|
+
* owns it) and I am the lowest-id instance. Skip when a live peer owns it. */
|
|
591
|
+
export function ownsMobileCommand(opts) {
|
|
592
|
+
if (!opts.projectSlug)
|
|
593
|
+
return true;
|
|
594
|
+
if (opts.projectSlug === opts.activeSlug)
|
|
595
|
+
return true;
|
|
596
|
+
if (!opts.hasBridge || opts.liveIds.length <= 1)
|
|
597
|
+
return true;
|
|
598
|
+
if (opts.peerSlugs.has(opts.projectSlug))
|
|
599
|
+
return false;
|
|
600
|
+
const lowest = [...opts.liveIds].sort()[0];
|
|
601
|
+
return opts.selfId !== '' && opts.selfId === lowest;
|
|
602
|
+
}
|
|
603
|
+
function startDesktopCommandConsumer(port, coord = {}) {
|
|
579
604
|
const deviceId = 'desktop-' + process.pid;
|
|
605
|
+
/* Multi-instance project integrity (Pablo 2026-06-23). The relay is keyed
|
|
606
|
+
* only by handle, so EVERY yf chat on the machine reads the SAME command
|
|
607
|
+
* stream -> a phone command would run on ALL of them (duplicate execution,
|
|
608
|
+
* N replies). With the per-turn project binding, the phone tags each chat
|
|
609
|
+
* command with its project_slug; this lets each instance decide if the
|
|
610
|
+
* command is "mine". Rule: run it when (a) no slug (legacy mobile, keep old
|
|
611
|
+
* broadcast), (b) the slug is MY active project, or (c) no OTHER live
|
|
612
|
+
* instance owns that project (single instance, or an orphan project where
|
|
613
|
+
* the lowest-id instance takes it). So exactly ONE instance runs each
|
|
614
|
+
* command. Decision is sync off a cached bridge snapshot refreshed below. */
|
|
615
|
+
let peerSnapshot = { slugs: new Set(), liveIds: [], hasBridge: false };
|
|
616
|
+
const PEER_FRESH_MS = 15_000;
|
|
617
|
+
const refreshPeers = async () => {
|
|
618
|
+
if (!coord.getBridgeState)
|
|
619
|
+
return;
|
|
620
|
+
try {
|
|
621
|
+
const st = await coord.getBridgeState();
|
|
622
|
+
if (!st || !Array.isArray(st.instances)) {
|
|
623
|
+
peerSnapshot = { slugs: new Set(), liveIds: [], hasBridge: false };
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const cut = Date.now() - PEER_FRESH_MS;
|
|
627
|
+
const live = st.instances.filter((i) => i && typeof i.last_seen === 'number' && i.last_seen >= cut);
|
|
628
|
+
const self = coord.getSelfId ? coord.getSelfId() : '';
|
|
629
|
+
peerSnapshot = {
|
|
630
|
+
slugs: new Set(live.filter((i) => i.id !== self).map((i) => String(i.project_slug || '')).filter(Boolean)),
|
|
631
|
+
liveIds: live.map((i) => i.id),
|
|
632
|
+
hasBridge: true,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
catch { /* keep the last snapshot on a transient failure */ }
|
|
636
|
+
};
|
|
637
|
+
const peerTimer = setInterval(() => { void refreshPeers(); }, 2500);
|
|
638
|
+
if (typeof peerTimer.unref === 'function')
|
|
639
|
+
peerTimer.unref();
|
|
640
|
+
const ownsChatCommand = (projectSlug) => ownsMobileCommand({
|
|
641
|
+
...(projectSlug ? { projectSlug } : {}),
|
|
642
|
+
activeSlug: coord.getActiveSlug ? (coord.getActiveSlug() ?? null) : null,
|
|
643
|
+
peerSlugs: peerSnapshot.slugs,
|
|
644
|
+
liveIds: peerSnapshot.liveIds,
|
|
645
|
+
selfId: coord.getSelfId ? coord.getSelfId() : '',
|
|
646
|
+
hasBridge: peerSnapshot.hasBridge,
|
|
647
|
+
});
|
|
580
648
|
const pollCommands = async (since) => {
|
|
581
649
|
const r = await relayPoll({ deviceId }, since, ['command']);
|
|
582
650
|
return {
|
|
@@ -683,6 +751,22 @@ function startDesktopCommandConsumer(port) {
|
|
|
683
751
|
const j = await resp.json().catch(() => ({}));
|
|
684
752
|
return j && j.active ? 'Turno interrumpido.' : 'No habia ningun turno en curso.';
|
|
685
753
|
}
|
|
754
|
+
if (action === 'voice_diag') {
|
|
755
|
+
/* Mobile/PWA mic diagnostic -> the SAME support buffer the panel uses,
|
|
756
|
+
* so the phone's mic failures are visible server-side (Pablo: "el mic
|
|
757
|
+
* web nunca anduvo, sacame el log del telefono"). Best-effort. */
|
|
758
|
+
try {
|
|
759
|
+
const { recordReport, buildVoiceDiagReport } = await import('../support/reports.js');
|
|
760
|
+
const rep = buildVoiceDiagReport(args);
|
|
761
|
+
if (!rep)
|
|
762
|
+
return 'voice_diag sin stage.';
|
|
763
|
+
await recordReport(rep);
|
|
764
|
+
return 'Diagnostico de mic registrado: ' + rep.message;
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
return 'No pude registrar el diagnostico de voz.';
|
|
768
|
+
}
|
|
769
|
+
}
|
|
686
770
|
return 'Accion no soportada: ' + action;
|
|
687
771
|
};
|
|
688
772
|
/* Two consumers over the same stream (task #29): the CHAT consumer
|
|
@@ -693,7 +777,9 @@ function startDesktopCommandConsumer(port) {
|
|
|
693
777
|
* past the skipped events. */
|
|
694
778
|
const chat = startCommandConsumer({
|
|
695
779
|
enabled: relayEnabled, intervalMs: 3000,
|
|
696
|
-
|
|
780
|
+
/* only MY project's chat commands (multi-instance integrity); the other
|
|
781
|
+
* instances' consumers advance their cursor past the skipped ones. */
|
|
782
|
+
accept: (c) => !c.control && ownsChatCommand(c.projectSlug),
|
|
697
783
|
pollCommands, runTurn, reply,
|
|
698
784
|
ackImmediate: true, /* confirm receipt before the turn runs */
|
|
699
785
|
onError: () => { },
|
|
@@ -705,8 +791,8 @@ function startDesktopCommandConsumer(port) {
|
|
|
705
791
|
onError: () => { },
|
|
706
792
|
});
|
|
707
793
|
return {
|
|
708
|
-
stop() { chat.stop(); control.stop(); },
|
|
709
|
-
tickOnce: async () => { await chat.tickOnce(); await control.tickOnce(); },
|
|
794
|
+
stop() { clearInterval(peerTimer); chat.stop(); control.stop(); },
|
|
795
|
+
tickOnce: async () => { await refreshPeers(); await chat.tickOnce(); await control.tickOnce(); },
|
|
710
796
|
};
|
|
711
797
|
}
|
|
712
798
|
/* Project context for board snapshots, set per chat turn (handleChat). */
|