@ours.network/fleet 1.2.0-nightly.41 → 1.2.0-nightly.42
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/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.2.0-nightly.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "1.2.0-nightly.42",
|
|
3
|
+
"buildId": "536c9cc8b8d7",
|
|
4
|
+
"commit": "c51b38eaa547d240e97f776e09286590222e727e",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-03T20:14:35.466Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"workspace.enroll.preserve-profile-v1",
|
|
9
9
|
"cowork.http-management-v1",
|
|
@@ -45,9 +45,9 @@ export async function requestMemberAddition(input) {
|
|
|
45
45
|
}
|
|
46
46
|
const task = getTask(taskId);
|
|
47
47
|
const room = task.room_id && getRoomRecord(task.room_id);
|
|
48
|
-
if (!room || !['active', 'review'].includes(task.state) || task.terminal_intent ||
|
|
48
|
+
if (!room || !['active', 'review'].includes(task.state) || task.terminal_intent || room.state !== 'active'
|
|
49
49
|
|| room.task_id !== taskId || room.room_identity_cid !== task.room_identity_cid)
|
|
50
|
-
throw new MemberAdditionRejected('Adding an agent requires an active
|
|
50
|
+
throw new MemberAdditionRejected('Adding an agent requires an active task room');
|
|
51
51
|
const roleId = `${taskId.slice(0, 8)}-${r.slot}-1`;
|
|
52
52
|
if (room.member_seats.some(s => s.role_name === roleId))
|
|
53
53
|
throw new MemberAdditionRejected('This member slot already exists');
|
|
@@ -379,9 +379,9 @@ export async function provisionMembers(input) {
|
|
|
379
379
|
if (input.append) {
|
|
380
380
|
const task = input.taskId ? getTask(input.taskId) : undefined;
|
|
381
381
|
const room = getRoomRecord(input.roomId);
|
|
382
|
-
if (!task || !['active', 'review'].includes(task.state) ||
|
|
382
|
+
if (!task || !['active', 'review'].includes(task.state) || room?.state !== 'active'
|
|
383
383
|
|| room.task_id !== task.task_id || room.room_identity_cid !== task.room_identity_cid)
|
|
384
|
-
throw new Error('Adding an agent requires the exact active
|
|
384
|
+
throw new Error('Adding an agent requires the exact active task room');
|
|
385
385
|
const names = expandMembers(input.template, shortId(task.task_id)).map(m => m.name);
|
|
386
386
|
if (room.member_seats.some(seat => names.includes(seat.role_name)))
|
|
387
387
|
throw new Error('Member slot is already recorded; inspect its existing launch');
|
package/dist/runner.js
CHANGED
|
@@ -626,6 +626,16 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
626
626
|
let unsubscribeRecovery;
|
|
627
627
|
let monitorLoop;
|
|
628
628
|
let sessionStartupComplete = false;
|
|
629
|
+
let startupRecoveryAllowed = false;
|
|
630
|
+
let successfulTurnObserved = false;
|
|
631
|
+
let monitorMayStart = false;
|
|
632
|
+
let monitorStarted = false;
|
|
633
|
+
const startMonitorAfterStartup = () => {
|
|
634
|
+
if (!monitorStarted && monitorMayStart && sessionStartupComplete && !deps.shouldStop?.()) {
|
|
635
|
+
monitorStarted = true;
|
|
636
|
+
monitorLoop = monitor?.run(pid);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
629
639
|
let sessionClosed = false;
|
|
630
640
|
let ownerChannel;
|
|
631
641
|
let ownerBinder;
|
|
@@ -654,13 +664,23 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
654
664
|
pid = agentSession.pid;
|
|
655
665
|
arbiter = new RoleTurnArbiter(agentSession);
|
|
656
666
|
sessionHandle = arbiter;
|
|
657
|
-
|
|
667
|
+
const unsubscribeFailure = agentSession.subscribe(event => {
|
|
658
668
|
if (event.kind !== 'error' || !event.text || event.origin?.kind === 'stall-watchdog')
|
|
659
669
|
return;
|
|
660
670
|
const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
|
|
661
671
|
if (evidence)
|
|
662
672
|
resolvedMonitorDeps.onFailureEvidence?.(evidence);
|
|
663
673
|
});
|
|
674
|
+
const unsubscribeStartup = agentSession.subscribeConversation?.(event => {
|
|
675
|
+
if (event.kind !== 'turn.completed' || !('outcome' in event.payload) || event.payload.outcome !== 'completed')
|
|
676
|
+
return;
|
|
677
|
+
successfulTurnObserved = true;
|
|
678
|
+
if (startupRecoveryAllowed) {
|
|
679
|
+
sessionStartupComplete = true;
|
|
680
|
+
startMonitorAfterStartup();
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
unsubscribeRecovery = () => { unsubscribeFailure(); unsubscribeStartup?.(); };
|
|
664
684
|
if (role.owner_channel) {
|
|
665
685
|
try {
|
|
666
686
|
ownerBinder = await deps.acquireOwnerBinder(dir, name, role.owner_channel.identity);
|
|
@@ -739,10 +759,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
739
759
|
// refusal or a cancellation reached the agent and was not acted on, so
|
|
740
760
|
// the monitor must keep its cursor and try again.
|
|
741
761
|
submit: async (text, options) => {
|
|
742
|
-
//
|
|
743
|
-
//
|
|
744
|
-
// steer into the live turn instead; after it completes, honor the
|
|
745
|
-
// configured interrupt policy normally.
|
|
762
|
+
// Delivery starts after the protected startup turn succeeds. Honor the
|
|
763
|
+
// configured interrupt policy for subsequent turns.
|
|
746
764
|
const policy = options?.interrupt;
|
|
747
765
|
const interrupt = policy === true && sessionStartupComplete;
|
|
748
766
|
const promptOptions = {
|
|
@@ -750,8 +768,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
750
768
|
...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
|
|
751
769
|
origin: { kind: 'fleet-monitor' },
|
|
752
770
|
};
|
|
753
|
-
//
|
|
754
|
-
// steer rather than waiting on/cancelling the runner-owned first turn.
|
|
771
|
+
// after_tool waits for a safe boundary in subsequent active turns.
|
|
755
772
|
const result = policy === 'after_tool' && sessionStartupComplete
|
|
756
773
|
? await arbiter.submitPromptAfterTool(text, promptOptions)
|
|
757
774
|
: await arbiter.submitPrompt(text, promptOptions);
|
|
@@ -787,10 +804,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
787
804
|
// startup prompt and then refuses it has not started; logging the role as
|
|
788
805
|
// up would hide a role that never read its briefing.
|
|
789
806
|
const starting = arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
|
|
790
|
-
//
|
|
791
|
-
//
|
|
792
|
-
// success, so there is neither a deaf gap nor a boot-cancellation loop.
|
|
793
|
-
monitorLoop = monitor?.run(pid);
|
|
807
|
+
// Keep the early cursor baseline; do not deliver wakes into the protected
|
|
808
|
+
// first turn. Steering can pre-empt startup just like cancellation.
|
|
794
809
|
// Owner traffic and agent replies must be observed during a long first
|
|
795
810
|
// turn. Ordinary owner input steers until startup has proved successful.
|
|
796
811
|
if (ownerChannel) {
|
|
@@ -850,7 +865,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
850
865
|
else if (interruptedForWake)
|
|
851
866
|
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
852
867
|
+ 'keeping temporary supervisor alive');
|
|
853
|
-
|
|
868
|
+
startupRecoveryAllowed = interruptedForWake;
|
|
869
|
+
sessionStartupComplete = started.succeeded || (interruptedForWake && successfulTurnObserved);
|
|
854
870
|
reloadLoopConfig = async () => {
|
|
855
871
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
856
872
|
const definitions = nextRole.loops ?? [];
|
|
@@ -907,9 +923,10 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
907
923
|
}
|
|
908
924
|
}
|
|
909
925
|
deps.log(`[${name}] up; pid=${pid} cwd=${runCwd} harness=${role.harness} session=${sessionBackend} mode=${mode}`);
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
|
|
926
|
+
// Keep arrivals behind the early prime() cursor; never re-prime at startup.
|
|
927
|
+
// Recoverable cancellation preserves the supervisor, not monitor readiness.
|
|
928
|
+
monitorMayStart = true;
|
|
929
|
+
startMonitorAfterStartup();
|
|
913
930
|
recoveryController = new RoleRecoveryController({
|
|
914
931
|
role: name, identity: role.identity, stateDir: dir, now: deps.now, sleep: deps.sleep,
|
|
915
932
|
log: deps.log,
|
|
@@ -1028,6 +1045,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1028
1045
|
}
|
|
1029
1046
|
}
|
|
1030
1047
|
}
|
|
1048
|
+
monitorMayStart = false;
|
|
1031
1049
|
if (loopManager) {
|
|
1032
1050
|
control?.setLoopManager(undefined);
|
|
1033
1051
|
await loopManager.stop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "1.2.0-nightly.
|
|
3
|
+
"version": "1.2.0-nightly.42",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|