crewly 1.20.358 → 1.20.359
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/backend/backend/src/constants.d.ts +59 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +59 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +21 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +162 -11
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/memory/agent-memory-refresh.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/agent-memory-refresh.service.js +5 -0
- package/dist/backend/backend/src/services/memory/agent-memory-refresh.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/startup-briefing.service.d.ts +10 -0
- package/dist/backend/backend/src/services/memory/startup-briefing.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/startup-briefing.service.js +65 -22
- package/dist/backend/backend/src/services/memory/startup-briefing.service.js.map +1 -1
- package/dist/backend/backend/src/services/session/session-binding.d.ts +25 -2
- package/dist/backend/backend/src/services/session/session-binding.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/session-binding.js +32 -4
- package/dist/backend/backend/src/services/session/session-binding.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +3 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-log-compile.service.d.ts +27 -2
- package/dist/backend/backend/src/services/wiki/wiki-log-compile.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-log-compile.service.js +105 -8
- package/dist/backend/backend/src/services/wiki/wiki-log-compile.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/work-record-transcript.d.ts +64 -0
- package/dist/backend/backend/src/services/wiki/work-record-transcript.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/work-record-transcript.js +194 -0
- package/dist/backend/backend/src/services/wiki/work-record-transcript.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/work-record.d.ts +239 -0
- package/dist/backend/backend/src/services/wiki/work-record.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/work-record.js +631 -0
- package/dist/backend/backend/src/services/wiki/work-record.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/work-record.service.d.ts +209 -0
- package/dist/backend/backend/src/services/wiki/work-record.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/work-record.service.js +514 -0
- package/dist/backend/backend/src/services/wiki/work-record.service.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +59 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +59 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/memory/startup-briefing.service.d.ts +10 -0
- package/dist/cli/backend/src/services/memory/startup-briefing.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/startup-briefing.service.js +65 -22
- package/dist/cli/backend/src/services/memory/startup-briefing.service.js.map +1 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.d.ts +3 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.js.map +1 -1
- package/dist/cli/backend/src/services/wiki/work-record-transcript.d.ts +64 -0
- package/dist/cli/backend/src/services/wiki/work-record-transcript.d.ts.map +1 -0
- package/dist/cli/backend/src/services/wiki/work-record-transcript.js +194 -0
- package/dist/cli/backend/src/services/wiki/work-record-transcript.js.map +1 -0
- package/dist/cli/backend/src/services/wiki/work-record.d.ts +239 -0
- package/dist/cli/backend/src/services/wiki/work-record.d.ts.map +1 -0
- package/dist/cli/backend/src/services/wiki/work-record.js +631 -0
- package/dist/cli/backend/src/services/wiki/work-record.js.map +1 -0
- package/package.json +1 -1
|
@@ -143,6 +143,27 @@ export declare class CrewlyServer {
|
|
|
143
143
|
* Runs after orchestrator auto-start so the orchestrator is available.
|
|
144
144
|
*/
|
|
145
145
|
private autoRestoreAgentSessionsIfEnabled;
|
|
146
|
+
/**
|
|
147
|
+
* Restore entries for owed agents that have no persisted session: built from
|
|
148
|
+
* the team config the way a team start would (member role and runtime, the
|
|
149
|
+
* team's first project as the working directory). A binding whose member is
|
|
150
|
+
* gone from the config by now is skipped.
|
|
151
|
+
*
|
|
152
|
+
* @param teams - Teams from storage
|
|
153
|
+
* @param bindings - Targets resolved to members (see selectAutoRestoreSessions)
|
|
154
|
+
* @returns Session entries in the shape the restore queue runs
|
|
155
|
+
*/
|
|
156
|
+
private persistedSessionsForBindings;
|
|
157
|
+
/**
|
|
158
|
+
* A boot restore was deferred by the running-agent cap. The agent is owed
|
|
159
|
+
* something that does not expire on its own (an owner thread, a promise,
|
|
160
|
+
* queued messages), so keep trying until a slot frees (crewly#1337's retry;
|
|
161
|
+
* the queue check is replaced because not every debt is a queued message).
|
|
162
|
+
* Never throws.
|
|
163
|
+
*
|
|
164
|
+
* @param sessionName - The agent whose restore was deferred
|
|
165
|
+
*/
|
|
166
|
+
private retryRestoreWhileOwed;
|
|
146
167
|
/**
|
|
147
168
|
* Check for and handle pending self-improvement from hot-reload.
|
|
148
169
|
* This runs at startup to validate or rollback any changes made
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../backend/src/index.ts"],"names":[],"mappings":";AA4CA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../backend/src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1087,6 +1087,50 @@ export class CrewlyServer {
|
|
|
1087
1087
|
error: refreshErr.message,
|
|
1088
1088
|
});
|
|
1089
1089
|
}
|
|
1090
|
+
// Work records (#1349): one structured `(record)` entry per finished
|
|
1091
|
+
// (or long-blocked) WorkItem in the project vault log — who asked, who
|
|
1092
|
+
// did it, outputs, errors, helpers, outcome — from the WorkItem, its
|
|
1093
|
+
// ticket, its children, the trace and the doer's transcript tail. The
|
|
1094
|
+
// digest, search and the startup briefing read them. No LLM. Non-fatal.
|
|
1095
|
+
try {
|
|
1096
|
+
const [{ WorkRecordService }, { ProjectTicketService }, { getTraceStore }] = await Promise.all([
|
|
1097
|
+
import('./services/wiki/work-record.service.js'),
|
|
1098
|
+
import('./services/project-tickets/project-ticket.service.js'),
|
|
1099
|
+
import('./services/trace/trace-store.js'),
|
|
1100
|
+
]);
|
|
1101
|
+
const pool = TaskPoolService.getInstance();
|
|
1102
|
+
const tickets = ProjectTicketService.getInstance();
|
|
1103
|
+
const records = new WorkRecordService({
|
|
1104
|
+
listWorkItems: () => pool.getAllItems(),
|
|
1105
|
+
findWorkItem: (id) => pool.findWorkItem(id),
|
|
1106
|
+
listTeams: () => this.storageService.getTeams(),
|
|
1107
|
+
listProjects: () => this.storageService.getProjects(),
|
|
1108
|
+
getTicket: (projectPath, id) => tickets.get(projectPath, id),
|
|
1109
|
+
getRequest: (id) => RequestService.getInstance().getById(id),
|
|
1110
|
+
readTrace: async (wi) => {
|
|
1111
|
+
const store = getTraceStore();
|
|
1112
|
+
const traceId = wi.traceId ?? store.traceByRef('workItem', wi.id);
|
|
1113
|
+
if (!traceId)
|
|
1114
|
+
return [];
|
|
1115
|
+
const all = await store.readAll(traceId);
|
|
1116
|
+
return all?.events ?? [];
|
|
1117
|
+
},
|
|
1118
|
+
locateTranscript: this.askTranscriptLocator(),
|
|
1119
|
+
eventBus: this.eventBusService,
|
|
1120
|
+
onTicketSettled: (listener) => tickets.onChange((change) => {
|
|
1121
|
+
const settled = change.ticket.status === 'done' || change.ticket.status === 'cancelled';
|
|
1122
|
+
if (settled && change.before.status !== change.ticket.status && change.ticket.workItemId)
|
|
1123
|
+
listener(change.ticket.workItemId);
|
|
1124
|
+
}),
|
|
1125
|
+
});
|
|
1126
|
+
WorkRecordService.setInstance(records);
|
|
1127
|
+
records.start();
|
|
1128
|
+
}
|
|
1129
|
+
catch (recordErr) {
|
|
1130
|
+
this.logger.warn('Work records failed to start (non-fatal)', {
|
|
1131
|
+
error: recordErr.message,
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1090
1134
|
// LLM-wiki → WorkItem bridge (2026-05-27): pending wiki queue
|
|
1091
1135
|
// items + legacy migration candidates become claimable
|
|
1092
1136
|
// WorkItems in the V3 pool. Replaces the bookkeep/reflect
|
|
@@ -4824,33 +4868,67 @@ export class CrewlyServer {
|
|
|
4824
4868
|
});
|
|
4825
4869
|
}
|
|
4826
4870
|
let targets = null;
|
|
4871
|
+
// Why each target other than a WorkItem is owed something; the reason
|
|
4872
|
+
// is logged in the restore plan for agents that were idle at shutdown.
|
|
4873
|
+
const owedReason = new Map();
|
|
4874
|
+
const noteOwed = (names, reason) => {
|
|
4875
|
+
const out = [];
|
|
4876
|
+
for (const name of names) {
|
|
4877
|
+
if (!owedReason.has(name))
|
|
4878
|
+
owedReason.set(name, reason);
|
|
4879
|
+
out.push(name);
|
|
4880
|
+
}
|
|
4881
|
+
return out;
|
|
4882
|
+
};
|
|
4827
4883
|
try {
|
|
4828
4884
|
const allItems = await TaskPoolService.getInstance().getAllItems();
|
|
4829
4885
|
// Work in hand only: active statuses, touched recently (see restore-filter),
|
|
4830
4886
|
// plus agents whose turn the last restart cut off.
|
|
4831
4887
|
const owed = await this.loadOwedWorkAtBoot();
|
|
4832
4888
|
targets = sessionsToRestore(allItems, [
|
|
4833
|
-
...this.interruptedTurnsAtBoot.map((t) => t.sessionName),
|
|
4889
|
+
...noteOwed(this.interruptedTurnsAtBoot.map((t) => t.sessionName), 'interrupted turn'),
|
|
4834
4890
|
// Agents with messages still waiting for them (restored from
|
|
4835
4891
|
// disk by the queue) — someone is owed an answer.
|
|
4836
|
-
...SubAgentMessageQueue.getInstance().sessionsWithPending(),
|
|
4892
|
+
...noteOwed(SubAgentMessageQueue.getInstance().sessionsWithPending(), 'queued messages'),
|
|
4837
4893
|
// Agents whose owner promise is past due and was never
|
|
4838
4894
|
// nudged: idle or not, they come back to be reminded once
|
|
4839
4895
|
// (2026-10-02, Eve). Not-yet-due and already-nudged
|
|
4840
4896
|
// promises are left to the open-items sweep.
|
|
4841
|
-
...owed.map((c) => c.sessionName),
|
|
4897
|
+
...noteOwed(owed.map((c) => c.sessionName), 'owed promise'),
|
|
4842
4898
|
// Agents an owner Slack thread waits on: a promised
|
|
4843
4899
|
// follow-up, an unanswered owner message, or an owner
|
|
4844
4900
|
// card they asked (2026-10-08: Atlas waited on a browser
|
|
4845
4901
|
// approval in the owner's thread and was skipped as "no
|
|
4846
4902
|
// work in hand").
|
|
4847
|
-
...(await this.ownerThreadSessionsAtBoot()),
|
|
4903
|
+
...noteOwed(await this.ownerThreadSessionsAtBoot(), 'owner thread waits on it'),
|
|
4848
4904
|
]);
|
|
4849
4905
|
}
|
|
4850
4906
|
catch (poolErr) {
|
|
4851
4907
|
this.logger.warn('Auto-restore could not query task pool; falling back to restoring every persisted session', { error: poolErr instanceof Error ? poolErr.message : String(poolErr) });
|
|
4852
4908
|
}
|
|
4853
|
-
|
|
4909
|
+
// A target that was not running at shutdown (idle-stopped earlier) is
|
|
4910
|
+
// still owed something and comes back from its team binding — unless
|
|
4911
|
+
// the owner stopped it, its team is paused, or its team moved to
|
|
4912
|
+
// another machine (crewly#1348: Pia, 2026-10-16).
|
|
4913
|
+
const { liveAgentSessions, movedAwayTeamIds } = await import('./services/messaging/owner-thread-sentinel.wiring.js');
|
|
4914
|
+
const live = teams ? liveAgentSessions(teams, movedAwayTeamIds(this.config.crewlyHome)) : new Set();
|
|
4915
|
+
const selection = selectAutoRestoreSessions({
|
|
4916
|
+
sessions: baselineSessions,
|
|
4917
|
+
teams,
|
|
4918
|
+
targets,
|
|
4919
|
+
heldBack: (name) => name === ORCHESTRATOR_SESSION_NAME || !live.has(name) || isOwnerStopped(name) || !!pausedTeamOfSession(name),
|
|
4920
|
+
});
|
|
4921
|
+
if (selection.droppedTargets.length > 0) {
|
|
4922
|
+
this.logger.info('Not restoring owed agents that were idle at shutdown: no live team member is bound to the name, or it is held back', {
|
|
4923
|
+
dropped: selection.droppedTargets,
|
|
4924
|
+
});
|
|
4925
|
+
}
|
|
4926
|
+
const idleAtShutdown = await this.persistedSessionsForBindings(teams ?? [], selection.fromTargets);
|
|
4927
|
+
if (idleAtShutdown.length > 0) {
|
|
4928
|
+
this.logger.info('Restoring owed agents that were idle at shutdown (not in the persisted state)', {
|
|
4929
|
+
sessions: idleAtShutdown.map((s) => `${s.name} (${owedReason.get(s.name) ?? 'work in hand'})`),
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4854
4932
|
if (selection.unbound.length > 0) {
|
|
4855
4933
|
this.logger.warn('Not auto-restoring sessions whose name no team member is bound to', {
|
|
4856
4934
|
unbound: selection.unbound,
|
|
@@ -4864,7 +4942,7 @@ export class CrewlyServer {
|
|
|
4864
4942
|
truncated: selection.skippedNoWork.length > 20,
|
|
4865
4943
|
});
|
|
4866
4944
|
}
|
|
4867
|
-
const agentSessions = selection.sessions;
|
|
4945
|
+
const agentSessions = [...selection.sessions, ...idleAtShutdown];
|
|
4868
4946
|
if (agentSessions.length === 0) {
|
|
4869
4947
|
this.logger.info('No persisted agent sessions to restore (all idle, no pending WorkItems)');
|
|
4870
4948
|
return;
|
|
@@ -4883,6 +4961,7 @@ export class CrewlyServer {
|
|
|
4883
4961
|
owedSince.set(c.sessionName, Math.min(owedSince.get(c.sessionName) ?? Infinity, Number.isFinite(due) ? due : 0));
|
|
4884
4962
|
}
|
|
4885
4963
|
const leadRoles = TEAM_LEAD_CONSTANTS.LEAD_ROLES;
|
|
4964
|
+
const wasIdle = new Set(idleAtShutdown.map((s) => s.name));
|
|
4886
4965
|
const entries = agentSessions.map((session) => {
|
|
4887
4966
|
const ownerTimes = queueMessages.peek(session.name).filter((m) => m.meta?.owner === true).map((m) => m.queuedAt);
|
|
4888
4967
|
if (owedSince.has(session.name))
|
|
@@ -4911,18 +4990,24 @@ export class CrewlyServer {
|
|
|
4911
4990
|
});
|
|
4912
4991
|
if (!result.success)
|
|
4913
4992
|
this.logger.warn('Failed to restore agent session', { name: session.name, error: result.error });
|
|
4993
|
+
// Every slot was taken: keep trying until one frees instead of
|
|
4994
|
+
// dropping the agent (the owner's thread is still waiting).
|
|
4995
|
+
if (!result.success && result.errorCode === RESOURCE_MODE_CONSTANTS.START_DEFERRED_ERROR_CODE)
|
|
4996
|
+
this.retryRestoreWhileOwed(session.name);
|
|
4914
4997
|
return { success: result.success };
|
|
4915
4998
|
},
|
|
4916
4999
|
onReady: () => this.sendRecoveryFor(session.name),
|
|
4917
5000
|
};
|
|
5001
|
+
// An agent that was idle at shutdown says in the plan what it is owed.
|
|
5002
|
+
const idle = wasIdle.has(session.name) ? ` (${owedReason.get(session.name) ?? 'work in hand'}; was idle at shutdown)` : '';
|
|
4918
5003
|
if (ownerTimes.length > 0) {
|
|
4919
|
-
return { ...base, tier: RESTORE_TIER.OWNER, ownerSince: Math.min(...ownerTimes), reason:
|
|
5004
|
+
return { ...base, tier: RESTORE_TIER.OWNER, ownerSince: Math.min(...ownerTimes), reason: `owner message waiting${idle}` };
|
|
4920
5005
|
}
|
|
4921
5006
|
if (session.role === ORCHESTRATOR_ROLE)
|
|
4922
5007
|
return { ...base, tier: RESTORE_TIER.ORCHESTRATOR, reason: 'orchestrator' };
|
|
4923
5008
|
if (leadRoles.includes(session.role ?? ''))
|
|
4924
|
-
return { ...base, tier: RESTORE_TIER.LEAD, reason:
|
|
4925
|
-
return { ...base, tier: RESTORE_TIER.OTHER, reason:
|
|
5009
|
+
return { ...base, tier: RESTORE_TIER.LEAD, reason: `team lead with work in hand${idle}` };
|
|
5010
|
+
return { ...base, tier: RESTORE_TIER.OTHER, reason: `work in hand${idle}` };
|
|
4926
5011
|
});
|
|
4927
5012
|
// Runs in the background; boot continues while agents come back.
|
|
4928
5013
|
void getRestoreQueue()
|
|
@@ -4942,6 +5027,71 @@ export class CrewlyServer {
|
|
|
4942
5027
|
// Don't fail startup if auto-restore fails
|
|
4943
5028
|
}
|
|
4944
5029
|
}
|
|
5030
|
+
/**
|
|
5031
|
+
* Restore entries for owed agents that have no persisted session: built from
|
|
5032
|
+
* the team config the way a team start would (member role and runtime, the
|
|
5033
|
+
* team's first project as the working directory). A binding whose member is
|
|
5034
|
+
* gone from the config by now is skipped.
|
|
5035
|
+
*
|
|
5036
|
+
* @param teams - Teams from storage
|
|
5037
|
+
* @param bindings - Targets resolved to members (see selectAutoRestoreSessions)
|
|
5038
|
+
* @returns Session entries in the shape the restore queue runs
|
|
5039
|
+
*/
|
|
5040
|
+
async persistedSessionsForBindings(teams, bindings) {
|
|
5041
|
+
if (bindings.length === 0)
|
|
5042
|
+
return [];
|
|
5043
|
+
let projectPaths = new Map();
|
|
5044
|
+
try {
|
|
5045
|
+
projectPaths = new Map((await this.storageService.getProjects()).map((p) => [p.id, p.path]));
|
|
5046
|
+
}
|
|
5047
|
+
catch (err) {
|
|
5048
|
+
this.logger.warn('Could not read projects for the owed agents; restoring them in the server directory', {
|
|
5049
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5050
|
+
});
|
|
5051
|
+
}
|
|
5052
|
+
const out = [];
|
|
5053
|
+
for (const binding of bindings) {
|
|
5054
|
+
const team = teams.find((t) => t.id === binding.teamId);
|
|
5055
|
+
const member = team?.members.find((m) => m.id === binding.memberId);
|
|
5056
|
+
if (!team || !member)
|
|
5057
|
+
continue;
|
|
5058
|
+
out.push({
|
|
5059
|
+
name: binding.name,
|
|
5060
|
+
cwd: (team.projectIds[0] && projectPaths.get(team.projectIds[0])) || process.cwd(),
|
|
5061
|
+
command: '',
|
|
5062
|
+
args: [],
|
|
5063
|
+
runtimeType: member.runtimeType,
|
|
5064
|
+
role: member.role,
|
|
5065
|
+
teamId: team.id,
|
|
5066
|
+
memberId: member.id,
|
|
5067
|
+
});
|
|
5068
|
+
}
|
|
5069
|
+
return out;
|
|
5070
|
+
}
|
|
5071
|
+
/**
|
|
5072
|
+
* A boot restore was deferred by the running-agent cap. The agent is owed
|
|
5073
|
+
* something that does not expire on its own (an owner thread, a promise,
|
|
5074
|
+
* queued messages), so keep trying until a slot frees (crewly#1337's retry;
|
|
5075
|
+
* the queue check is replaced because not every debt is a queued message).
|
|
5076
|
+
* Never throws.
|
|
5077
|
+
*
|
|
5078
|
+
* @param sessionName - The agent whose restore was deferred
|
|
5079
|
+
*/
|
|
5080
|
+
retryRestoreWhileOwed(sessionName) {
|
|
5081
|
+
void (async () => {
|
|
5082
|
+
const { scheduleDeferredStartRetry } = await import('./services/messaging/deferred-start-retry.js');
|
|
5083
|
+
const { activateAgentBySession } = await import('./controllers/team/team.controller.js');
|
|
5084
|
+
await scheduleDeferredStartRetry(sessionName, {
|
|
5085
|
+
activate: (n) => activateAgentBySession(this.apiController, n),
|
|
5086
|
+
hasPending: (n) => !isOwnerStopped(n),
|
|
5087
|
+
});
|
|
5088
|
+
})().catch((err) => {
|
|
5089
|
+
this.logger.warn('Could not retry a deferred boot restore', {
|
|
5090
|
+
sessionName,
|
|
5091
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5092
|
+
});
|
|
5093
|
+
});
|
|
5094
|
+
}
|
|
4945
5095
|
/**
|
|
4946
5096
|
* Check for and handle pending self-improvement from hot-reload.
|
|
4947
5097
|
* This runs at startup to validate or rollback any changes made
|
|
@@ -6899,13 +7049,14 @@ export class CrewlyServer {
|
|
|
6899
7049
|
* (with its messages persisted) was never started again (Pia, 2026-10-16).
|
|
6900
7050
|
*/
|
|
6901
7051
|
wakeQueuedAgentsAfterBoot() {
|
|
6902
|
-
const
|
|
7052
|
+
const restoreQueue = getRestoreQueue();
|
|
6903
7053
|
const backend = getSessionBackendSync();
|
|
6904
7054
|
const registration = this.apiController.agentRegistrationService;
|
|
6905
7055
|
const sessions = SubAgentMessageQueue.getInstance()
|
|
6906
7056
|
.sessionsWithPending()
|
|
6907
7057
|
.filter((name) => name !== ORCHESTRATOR_SESSION_NAME &&
|
|
6908
|
-
|
|
7058
|
+
// Waiting in the restore queue or being started by it right now
|
|
7059
|
+
!restoreQueue.isRestoring(name) &&
|
|
6909
7060
|
!isOwnerStopped(name) &&
|
|
6910
7061
|
!pausedTeamOfSession(name) &&
|
|
6911
7062
|
!backend?.sessionExists(name) &&
|