@tiflis-io/tiflis-code-workstation 0.3.10 → 0.3.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/dist/main.js +437 -292
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -118,11 +118,11 @@ var EnvSchema = z.object({
|
|
|
118
118
|
// Hide base agent options in mobile apps (only show aliases)
|
|
119
119
|
// ─────────────────────────────────────────────────────────────
|
|
120
120
|
/** Hide base Cursor agent option (only show aliases) */
|
|
121
|
-
HIDE_BASE_CURSOR: z.string().transform((val) => val
|
|
121
|
+
HIDE_BASE_CURSOR: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
|
|
122
122
|
/** Hide base Claude agent option (only show aliases) */
|
|
123
|
-
HIDE_BASE_CLAUDE: z.string().transform((val) => val
|
|
123
|
+
HIDE_BASE_CLAUDE: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
|
|
124
124
|
/** Hide base OpenCode agent option (only show aliases) */
|
|
125
|
-
HIDE_BASE_OPENCODE: z.string().transform((val) => val
|
|
125
|
+
HIDE_BASE_OPENCODE: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
|
|
126
126
|
// ─────────────────────────────────────────────────────────────
|
|
127
127
|
// Terminal Configuration
|
|
128
128
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -134,7 +134,7 @@ var EnvSchema = z.object({
|
|
|
134
134
|
// Mock Mode Configuration (for screenshot automation)
|
|
135
135
|
// ─────────────────────────────────────────────────────────────
|
|
136
136
|
/** Enable mock mode for screenshot automation tests */
|
|
137
|
-
MOCK_MODE: z.string().transform((val) => val
|
|
137
|
+
MOCK_MODE: z.string().transform((val) => val.toLowerCase() === "true").default("false"),
|
|
138
138
|
/** Path to mock fixtures directory (defaults to built-in fixtures) */
|
|
139
139
|
MOCK_FIXTURES_PATH: z.string().optional()
|
|
140
140
|
});
|
|
@@ -155,6 +155,7 @@ function parseAgentAliases() {
|
|
|
155
155
|
let commandStartIndex = 0;
|
|
156
156
|
for (let i = 0; i < parts.length; i++) {
|
|
157
157
|
const part = parts[i];
|
|
158
|
+
if (!part) break;
|
|
158
159
|
const eqIndex = part.indexOf("=");
|
|
159
160
|
if (eqIndex > 0 && !part.startsWith("-")) {
|
|
160
161
|
const varName = part.slice(0, eqIndex);
|
|
@@ -173,6 +174,10 @@ function parseAgentAliases() {
|
|
|
173
174
|
continue;
|
|
174
175
|
}
|
|
175
176
|
const baseCommand = commandParts[0];
|
|
177
|
+
if (!baseCommand) {
|
|
178
|
+
console.warn(`Invalid agent alias ${key}: empty base command`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
176
181
|
const additionalArgs = commandParts.slice(1);
|
|
177
182
|
aliases.set(aliasName, {
|
|
178
183
|
name: aliasName,
|
|
@@ -1144,8 +1149,9 @@ var SyncMessageSchema = z2.object({
|
|
|
1144
1149
|
// If true, excludes message histories (for watchOS)
|
|
1145
1150
|
});
|
|
1146
1151
|
var HistoryRequestPayloadSchema = z2.object({
|
|
1147
|
-
session_id: z2.string().optional()
|
|
1148
|
-
|
|
1152
|
+
session_id: z2.string().nullable().optional(),
|
|
1153
|
+
before_sequence: z2.number().int().optional(),
|
|
1154
|
+
limit: z2.number().int().min(1).max(50).optional()
|
|
1149
1155
|
});
|
|
1150
1156
|
var HistoryRequestSchema = z2.object({
|
|
1151
1157
|
type: z2.literal("history.request"),
|
|
@@ -1188,7 +1194,7 @@ var TerminateSessionSchema = z2.object({
|
|
|
1188
1194
|
var SupervisorCommandPayloadSchema = z2.object({
|
|
1189
1195
|
command: z2.string().optional(),
|
|
1190
1196
|
audio: z2.string().optional(),
|
|
1191
|
-
audio_format: z2.enum(["m4a", "wav", "mp3"]).optional(),
|
|
1197
|
+
audio_format: z2.enum(["m4a", "wav", "mp3", "webm", "opus"]).optional(),
|
|
1192
1198
|
message_id: z2.string().optional(),
|
|
1193
1199
|
language: z2.string().optional()
|
|
1194
1200
|
}).refine(
|
|
@@ -1232,7 +1238,7 @@ var SessionExecutePayloadSchema = z2.object({
|
|
|
1232
1238
|
text: z2.string().optional(),
|
|
1233
1239
|
// Alias for content (backward compat)
|
|
1234
1240
|
audio: z2.string().optional(),
|
|
1235
|
-
audio_format: z2.enum(["m4a", "wav", "mp3"]).optional(),
|
|
1241
|
+
audio_format: z2.enum(["m4a", "wav", "mp3", "webm", "opus"]).optional(),
|
|
1236
1242
|
message_id: z2.string().optional(),
|
|
1237
1243
|
// For linking transcription back to voice message
|
|
1238
1244
|
language: z2.string().optional(),
|
|
@@ -1365,6 +1371,13 @@ function parseClientMessage(data) {
|
|
|
1365
1371
|
}
|
|
1366
1372
|
return void 0;
|
|
1367
1373
|
}
|
|
1374
|
+
function parseClientMessageWithErrors(data) {
|
|
1375
|
+
const result = IncomingClientMessageSchema.safeParse(data);
|
|
1376
|
+
if (result.success) {
|
|
1377
|
+
return { success: true, data: result.data };
|
|
1378
|
+
}
|
|
1379
|
+
return { success: false, errors: result.error.issues };
|
|
1380
|
+
}
|
|
1368
1381
|
function parseTunnelMessage(data) {
|
|
1369
1382
|
const result = IncomingTunnelMessageSchema.safeParse(data);
|
|
1370
1383
|
if (result.success) {
|
|
@@ -2315,7 +2328,7 @@ var FileSystemWorkspaceDiscovery = class {
|
|
|
2315
2328
|
}
|
|
2316
2329
|
try {
|
|
2317
2330
|
execSync(`git merge "${sourceBranch}"`, { cwd: projectPath });
|
|
2318
|
-
} catch
|
|
2331
|
+
} catch {
|
|
2319
2332
|
const conflicts = execSync("git diff --name-only --diff-filter=U", {
|
|
2320
2333
|
cwd: projectPath,
|
|
2321
2334
|
encoding: "utf-8"
|
|
@@ -3283,8 +3296,8 @@ function mergeToolBlocks(blocks) {
|
|
|
3283
3296
|
metadata: {
|
|
3284
3297
|
tool_name: block.metadata.tool_name || existing.metadata.tool_name,
|
|
3285
3298
|
tool_use_id: toolUseId,
|
|
3286
|
-
tool_input: block.metadata.tool_input
|
|
3287
|
-
tool_output: block.metadata.tool_output
|
|
3299
|
+
tool_input: block.metadata.tool_input ?? existing.metadata.tool_input,
|
|
3300
|
+
tool_output: block.metadata.tool_output ?? existing.metadata.tool_output,
|
|
3288
3301
|
tool_status: mergedStatus
|
|
3289
3302
|
}
|
|
3290
3303
|
};
|
|
@@ -3341,8 +3354,8 @@ function accumulateBlocks(existing, newBlocks) {
|
|
|
3341
3354
|
metadata: {
|
|
3342
3355
|
tool_name: block.metadata.tool_name || existingBlock.metadata.tool_name,
|
|
3343
3356
|
tool_use_id: toolUseId,
|
|
3344
|
-
tool_input: block.metadata.tool_input
|
|
3345
|
-
tool_output: block.metadata.tool_output
|
|
3357
|
+
tool_input: block.metadata.tool_input ?? existingBlock.metadata.tool_input,
|
|
3358
|
+
tool_output: block.metadata.tool_output ?? existingBlock.metadata.tool_output,
|
|
3346
3359
|
tool_status: mergedStatus
|
|
3347
3360
|
}
|
|
3348
3361
|
};
|
|
@@ -4022,7 +4035,7 @@ var AgentSessionManager = class extends EventEmitter2 {
|
|
|
4022
4035
|
const worktreePath = `/${workspace}/${project}--${branch}`;
|
|
4023
4036
|
const terminatedSessions = [];
|
|
4024
4037
|
for (const [sessionId, state] of this.sessions) {
|
|
4025
|
-
const isInWorktree = state.workingDir.includes(worktreePath) || state.cliSessionId?.includes(`${project}--${branch}`) || state.workingDir.endsWith(`${project}--${branch}`);
|
|
4038
|
+
const isInWorktree = state.workingDir.includes(worktreePath) || (state.cliSessionId?.includes(`${project}--${branch}`) ?? false) || state.workingDir.endsWith(`${project}--${branch}`);
|
|
4026
4039
|
if (isInWorktree) {
|
|
4027
4040
|
try {
|
|
4028
4041
|
if (state.isExecuting) {
|
|
@@ -4044,7 +4057,7 @@ var AgentSessionManager = class extends EventEmitter2 {
|
|
|
4044
4057
|
getWorktreeSessionSummary(workspace, project, branch) {
|
|
4045
4058
|
const worktreePath = `/${workspace}/${project}--${branch}`;
|
|
4046
4059
|
const activeSessions = Array.from(this.sessions.values()).filter(
|
|
4047
|
-
(session) => session.workingDir.includes(worktreePath) || session.cliSessionId?.includes(`${project}--${branch}`) || session.workingDir.endsWith(`${project}--${branch}`)
|
|
4060
|
+
(session) => session.workingDir.includes(worktreePath) || (session.cliSessionId?.includes(`${project}--${branch}`) ?? false) || session.workingDir.endsWith(`${project}--${branch}`)
|
|
4048
4061
|
);
|
|
4049
4062
|
const sessionTypes = [...new Set(activeSessions.map((s) => s.agentType))];
|
|
4050
4063
|
const executingCount = activeSessions.filter((s) => s.isExecuting).length;
|
|
@@ -4640,8 +4653,8 @@ var CreateSessionUseCase = class {
|
|
|
4640
4653
|
*/
|
|
4641
4654
|
checkSessionLimits(sessionType) {
|
|
4642
4655
|
if (sessionType === "terminal") {
|
|
4643
|
-
const
|
|
4644
|
-
if (
|
|
4656
|
+
const count2 = this.deps.sessionManager.countByType("terminal");
|
|
4657
|
+
if (count2 >= SESSION_CONFIG.MAX_TERMINAL_SESSIONS) {
|
|
4645
4658
|
throw new SessionLimitReachedError("terminal", SESSION_CONFIG.MAX_TERMINAL_SESSIONS);
|
|
4646
4659
|
}
|
|
4647
4660
|
} else if (sessionType !== "supervisor") {
|
|
@@ -4691,6 +4704,9 @@ var TerminateSessionUseCase = class {
|
|
|
4691
4704
|
let terminatedInMemory = false;
|
|
4692
4705
|
let terminatedInDb = false;
|
|
4693
4706
|
if (session) {
|
|
4707
|
+
if (isAgentType(session.type)) {
|
|
4708
|
+
this.deps.agentSessionManager.terminateSession(sessionId);
|
|
4709
|
+
}
|
|
4694
4710
|
await this.deps.sessionManager.terminateSession(id);
|
|
4695
4711
|
terminatedInMemory = true;
|
|
4696
4712
|
this.logger.info(
|
|
@@ -4729,6 +4745,42 @@ var TerminateSessionUseCase = class {
|
|
|
4729
4745
|
};
|
|
4730
4746
|
return { response, broadcast };
|
|
4731
4747
|
}
|
|
4748
|
+
/**
|
|
4749
|
+
* Terminates a session and broadcasts the termination to all clients.
|
|
4750
|
+
* Use this for internal calls (e.g., from supervisor tools) where no request/response is needed.
|
|
4751
|
+
* Returns true if session was found and terminated, false otherwise.
|
|
4752
|
+
*/
|
|
4753
|
+
async terminateAndBroadcast(sessionId) {
|
|
4754
|
+
const id = new SessionId(sessionId);
|
|
4755
|
+
this.logger.info({ sessionId }, "Attempting to terminate session (internal)");
|
|
4756
|
+
const session = this.deps.sessionManager.getSession(id);
|
|
4757
|
+
if (session?.type === "supervisor") {
|
|
4758
|
+
throw new Error("Cannot terminate supervisor session");
|
|
4759
|
+
}
|
|
4760
|
+
let terminatedInMemory = false;
|
|
4761
|
+
let terminatedInDb = false;
|
|
4762
|
+
if (session) {
|
|
4763
|
+
if (isAgentType(session.type)) {
|
|
4764
|
+
this.deps.agentSessionManager.terminateSession(sessionId);
|
|
4765
|
+
}
|
|
4766
|
+
await this.deps.sessionManager.terminateSession(id);
|
|
4767
|
+
terminatedInMemory = true;
|
|
4768
|
+
}
|
|
4769
|
+
terminatedInDb = this.deps.chatHistoryService.terminateSession(sessionId);
|
|
4770
|
+
if (!terminatedInMemory && !terminatedInDb) {
|
|
4771
|
+
return false;
|
|
4772
|
+
}
|
|
4773
|
+
const broadcast = {
|
|
4774
|
+
type: "session.terminated",
|
|
4775
|
+
session_id: sessionId
|
|
4776
|
+
};
|
|
4777
|
+
this.deps.messageBroadcaster.broadcastToAll(JSON.stringify(broadcast));
|
|
4778
|
+
this.logger.info(
|
|
4779
|
+
{ sessionId, terminatedInMemory, terminatedInDb },
|
|
4780
|
+
"Session terminated and broadcast sent"
|
|
4781
|
+
);
|
|
4782
|
+
return true;
|
|
4783
|
+
}
|
|
4732
4784
|
};
|
|
4733
4785
|
|
|
4734
4786
|
// src/application/queries/list-sessions.ts
|
|
@@ -5040,7 +5092,7 @@ var MessageBroadcasterImpl = class {
|
|
|
5040
5092
|
};
|
|
5041
5093
|
|
|
5042
5094
|
// src/infrastructure/persistence/repositories/message-repository.ts
|
|
5043
|
-
import { eq as eq3, desc, gt, and as and2, max } from "drizzle-orm";
|
|
5095
|
+
import { eq as eq3, desc, gt, lt, and as and2, max, count } from "drizzle-orm";
|
|
5044
5096
|
import { nanoid as nanoid2 } from "nanoid";
|
|
5045
5097
|
var MessageRepository = class {
|
|
5046
5098
|
/**
|
|
@@ -5066,17 +5118,24 @@ var MessageRepository = class {
|
|
|
5066
5118
|
db2.insert(messages).values(newMessage).run();
|
|
5067
5119
|
return { ...newMessage, createdAt: newMessage.createdAt };
|
|
5068
5120
|
}
|
|
5069
|
-
/**
|
|
5070
|
-
* Gets messages for a session with pagination.
|
|
5071
|
-
* Returns messages ordered by sequence descending (newest first).
|
|
5072
|
-
*/
|
|
5073
5121
|
getBySession(sessionId, limit = 100) {
|
|
5074
5122
|
const db2 = getDatabase();
|
|
5075
5123
|
return db2.select().from(messages).where(eq3(messages.sessionId, sessionId)).orderBy(desc(messages.sequence)).limit(limit).all();
|
|
5076
5124
|
}
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5125
|
+
getBySessionPaginated(sessionId, options = {}) {
|
|
5126
|
+
const db2 = getDatabase();
|
|
5127
|
+
const limit = Math.min(options.limit ?? 20, 50);
|
|
5128
|
+
const totalResult = db2.select({ count: count() }).from(messages).where(eq3(messages.sessionId, sessionId)).get();
|
|
5129
|
+
const totalCount = totalResult?.count ?? 0;
|
|
5130
|
+
const whereClause = options.beforeSequence ? and2(
|
|
5131
|
+
eq3(messages.sessionId, sessionId),
|
|
5132
|
+
lt(messages.sequence, options.beforeSequence)
|
|
5133
|
+
) : eq3(messages.sessionId, sessionId);
|
|
5134
|
+
const rows = db2.select().from(messages).where(whereClause).orderBy(desc(messages.sequence)).limit(limit + 1).all();
|
|
5135
|
+
const hasMore = rows.length > limit;
|
|
5136
|
+
const resultMessages = hasMore ? rows.slice(0, limit) : rows;
|
|
5137
|
+
return { messages: resultMessages, hasMore, totalCount };
|
|
5138
|
+
}
|
|
5080
5139
|
getAfterTimestamp(sessionId, timestamp, limit = 100) {
|
|
5081
5140
|
const db2 = getDatabase();
|
|
5082
5141
|
return db2.select().from(messages).where(and2(eq3(messages.sessionId, sessionId), gt(messages.createdAt, timestamp))).orderBy(messages.createdAt).limit(limit).all();
|
|
@@ -5766,9 +5825,40 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5766
5825
|
};
|
|
5767
5826
|
});
|
|
5768
5827
|
}
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5828
|
+
getSupervisorHistoryPaginated(options = {}) {
|
|
5829
|
+
const sessionId = _ChatHistoryService.SUPERVISOR_SESSION_ID;
|
|
5830
|
+
const result = this.messageRepo.getBySessionPaginated(sessionId, options);
|
|
5831
|
+
const messages2 = result.messages.reverse().map((row) => {
|
|
5832
|
+
let contentBlocks;
|
|
5833
|
+
if (row.contentBlocks) {
|
|
5834
|
+
try {
|
|
5835
|
+
contentBlocks = JSON.parse(row.contentBlocks);
|
|
5836
|
+
} catch {
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
return {
|
|
5840
|
+
id: row.id,
|
|
5841
|
+
sessionId: row.sessionId,
|
|
5842
|
+
sequence: row.sequence,
|
|
5843
|
+
role: row.role,
|
|
5844
|
+
contentType: row.contentType,
|
|
5845
|
+
content: row.content,
|
|
5846
|
+
contentBlocks,
|
|
5847
|
+
audioInputPath: row.audioInputPath,
|
|
5848
|
+
audioOutputPath: row.audioOutputPath,
|
|
5849
|
+
isComplete: row.isComplete ?? false,
|
|
5850
|
+
createdAt: row.createdAt
|
|
5851
|
+
};
|
|
5852
|
+
});
|
|
5853
|
+
const firstMsg = messages2[0];
|
|
5854
|
+
const lastMsg = messages2[messages2.length - 1];
|
|
5855
|
+
return {
|
|
5856
|
+
messages: messages2,
|
|
5857
|
+
hasMore: result.hasMore,
|
|
5858
|
+
oldestSequence: firstMsg?.sequence,
|
|
5859
|
+
newestSequence: lastMsg?.sequence
|
|
5860
|
+
};
|
|
5861
|
+
}
|
|
5772
5862
|
clearSupervisorHistory() {
|
|
5773
5863
|
const sessionId = _ChatHistoryService.SUPERVISOR_SESSION_ID;
|
|
5774
5864
|
this.messageRepo.deleteBySession(sessionId);
|
|
@@ -5851,9 +5941,43 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5851
5941
|
};
|
|
5852
5942
|
});
|
|
5853
5943
|
}
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5944
|
+
getAgentHistoryPaginated(sessionId, options = {}) {
|
|
5945
|
+
const result = this.messageRepo.getBySessionPaginated(sessionId, options);
|
|
5946
|
+
const storedMessages = result.messages.reverse().map((row) => {
|
|
5947
|
+
let contentBlocks;
|
|
5948
|
+
if (row.contentBlocks) {
|
|
5949
|
+
try {
|
|
5950
|
+
contentBlocks = JSON.parse(row.contentBlocks);
|
|
5951
|
+
} catch (error) {
|
|
5952
|
+
this.logger.error(
|
|
5953
|
+
{ sessionId, messageId: row.id, error },
|
|
5954
|
+
"Failed to parse contentBlocks JSON"
|
|
5955
|
+
);
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
return {
|
|
5959
|
+
id: row.id,
|
|
5960
|
+
sessionId: row.sessionId,
|
|
5961
|
+
sequence: row.sequence,
|
|
5962
|
+
role: row.role,
|
|
5963
|
+
contentType: row.contentType,
|
|
5964
|
+
content: row.content,
|
|
5965
|
+
contentBlocks,
|
|
5966
|
+
audioInputPath: row.audioInputPath,
|
|
5967
|
+
audioOutputPath: row.audioOutputPath,
|
|
5968
|
+
isComplete: row.isComplete ?? false,
|
|
5969
|
+
createdAt: row.createdAt
|
|
5970
|
+
};
|
|
5971
|
+
});
|
|
5972
|
+
const firstMsg = storedMessages[0];
|
|
5973
|
+
const lastMsg = storedMessages[storedMessages.length - 1];
|
|
5974
|
+
return {
|
|
5975
|
+
messages: storedMessages,
|
|
5976
|
+
hasMore: result.hasMore,
|
|
5977
|
+
oldestSequence: firstMsg?.sequence,
|
|
5978
|
+
newestSequence: lastMsg?.sequence
|
|
5979
|
+
};
|
|
5980
|
+
}
|
|
5857
5981
|
clearAgentHistory(sessionId) {
|
|
5858
5982
|
this.messageRepo.deleteBySession(sessionId);
|
|
5859
5983
|
this.logger.info({ sessionId }, "Agent session history cleared");
|
|
@@ -6387,18 +6511,35 @@ var InMemorySessionManager = class extends EventEmitter3 {
|
|
|
6387
6511
|
this.logger.info({ sessionId: sessionId.value }, "Session terminated");
|
|
6388
6512
|
}
|
|
6389
6513
|
/**
|
|
6390
|
-
* Terminates all sessions.
|
|
6514
|
+
* Terminates all sessions with individual timeouts.
|
|
6515
|
+
* Each session has a 3-second timeout to prevent hanging.
|
|
6391
6516
|
*/
|
|
6392
6517
|
async terminateAll() {
|
|
6393
6518
|
this.agentSessionManager.cleanup();
|
|
6394
6519
|
const sessions2 = Array.from(this.sessions.values());
|
|
6520
|
+
const sessionCount = sessions2.length;
|
|
6521
|
+
if (sessionCount === 0) {
|
|
6522
|
+
this.logger.info("No sessions to terminate");
|
|
6523
|
+
return;
|
|
6524
|
+
}
|
|
6525
|
+
this.logger.info({ count: sessionCount }, "Terminating all sessions...");
|
|
6526
|
+
const INDIVIDUAL_TIMEOUT_MS = 3e3;
|
|
6395
6527
|
await Promise.all(
|
|
6396
6528
|
sessions2.map(async (session) => {
|
|
6529
|
+
const sessionId = session.id.value;
|
|
6397
6530
|
try {
|
|
6398
|
-
|
|
6531
|
+
const terminatePromise = session.terminate();
|
|
6532
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
6533
|
+
setTimeout(() => {
|
|
6534
|
+
this.logger.warn({ sessionId }, "Session termination timed out, skipping");
|
|
6535
|
+
resolve2();
|
|
6536
|
+
}, INDIVIDUAL_TIMEOUT_MS);
|
|
6537
|
+
});
|
|
6538
|
+
await Promise.race([terminatePromise, timeoutPromise]);
|
|
6539
|
+
this.logger.debug({ sessionId }, "Session terminated");
|
|
6399
6540
|
} catch (error) {
|
|
6400
6541
|
this.logger.error(
|
|
6401
|
-
{ sessionId
|
|
6542
|
+
{ sessionId, error },
|
|
6402
6543
|
"Error terminating session"
|
|
6403
6544
|
);
|
|
6404
6545
|
}
|
|
@@ -6406,7 +6547,7 @@ var InMemorySessionManager = class extends EventEmitter3 {
|
|
|
6406
6547
|
);
|
|
6407
6548
|
this.sessions.clear();
|
|
6408
6549
|
this.supervisorSession = null;
|
|
6409
|
-
this.logger.info({ count:
|
|
6550
|
+
this.logger.info({ count: sessionCount }, "All sessions terminated");
|
|
6410
6551
|
}
|
|
6411
6552
|
/**
|
|
6412
6553
|
* Gets the count of active sessions by type.
|
|
@@ -6857,16 +6998,7 @@ Steps executed:
|
|
|
6857
6998
|
// src/infrastructure/agents/supervisor/tools/session-tools.ts
|
|
6858
6999
|
import { tool as tool3 } from "@langchain/core/tools";
|
|
6859
7000
|
import { z as z5 } from "zod";
|
|
6860
|
-
function createSessionTools(sessionManager, agentSessionManager, workspaceDiscovery, workspacesRoot,
|
|
6861
|
-
const broadcastTermination = (sessionId) => {
|
|
6862
|
-
const broadcaster = getMessageBroadcaster?.();
|
|
6863
|
-
if (!broadcaster) return;
|
|
6864
|
-
const message = {
|
|
6865
|
-
type: "session.terminated",
|
|
6866
|
-
session_id: sessionId
|
|
6867
|
-
};
|
|
6868
|
-
broadcaster.broadcastToAll(JSON.stringify(message));
|
|
6869
|
-
};
|
|
7001
|
+
function createSessionTools(sessionManager, agentSessionManager, workspaceDiscovery, workspacesRoot, _getMessageBroadcaster, getChatHistoryService, clearSupervisorContext, terminateSessionCallback) {
|
|
6870
7002
|
const listSessions = tool3(
|
|
6871
7003
|
() => {
|
|
6872
7004
|
const chatHistoryService = getChatHistoryService?.();
|
|
@@ -7008,26 +7140,13 @@ Use this tool when user asks to "open terminal", "create terminal", or similar r
|
|
|
7008
7140
|
const terminateSession = tool3(
|
|
7009
7141
|
async ({ sessionId }) => {
|
|
7010
7142
|
try {
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
const session = sessionManager.getSession(id);
|
|
7014
|
-
let terminatedInMemory = false;
|
|
7015
|
-
let terminatedInDb = false;
|
|
7016
|
-
if (session) {
|
|
7017
|
-
if (isAgentType(session.type)) {
|
|
7018
|
-
agentSessionManager.terminateSession(sessionId);
|
|
7019
|
-
}
|
|
7020
|
-
await sessionManager.terminateSession(id);
|
|
7021
|
-
terminatedInMemory = true;
|
|
7022
|
-
}
|
|
7023
|
-
const chatHistoryService = getChatHistoryService?.();
|
|
7024
|
-
if (chatHistoryService) {
|
|
7025
|
-
terminatedInDb = chatHistoryService.terminateSession(sessionId);
|
|
7143
|
+
if (!terminateSessionCallback) {
|
|
7144
|
+
return "Error: Terminate session callback not configured.";
|
|
7026
7145
|
}
|
|
7027
|
-
|
|
7146
|
+
const terminated = await terminateSessionCallback(sessionId);
|
|
7147
|
+
if (!terminated) {
|
|
7028
7148
|
return `Session "${sessionId}" not found.`;
|
|
7029
7149
|
}
|
|
7030
|
-
broadcastTermination(sessionId);
|
|
7031
7150
|
return `Session "${sessionId}" terminated.`;
|
|
7032
7151
|
} catch (error) {
|
|
7033
7152
|
return `Error terminating session: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -7044,6 +7163,9 @@ Use this tool when user asks to "open terminal", "create terminal", or similar r
|
|
|
7044
7163
|
const terminateAllSessions = tool3(
|
|
7045
7164
|
async ({ sessionType }) => {
|
|
7046
7165
|
try {
|
|
7166
|
+
if (!terminateSessionCallback) {
|
|
7167
|
+
return "Error: Terminate session callback not configured.";
|
|
7168
|
+
}
|
|
7047
7169
|
const typeFilter = sessionType === "all" || !sessionType ? null : sessionType;
|
|
7048
7170
|
const chatHistoryService = getChatHistoryService?.();
|
|
7049
7171
|
const inMemorySessions = sessionManager.getAllSessions();
|
|
@@ -7051,31 +7173,25 @@ Use this tool when user asks to "open terminal", "create terminal", or similar r
|
|
|
7051
7173
|
const persistedSessions = chatHistoryService?.getActiveAgentSessions() ?? [];
|
|
7052
7174
|
const inMemoryIds = new Set(inMemorySessions.map((s) => s.id.value));
|
|
7053
7175
|
const persistedToTerminate = persistedSessions.filter((s) => !inMemoryIds.has(s.sessionId)).filter((s) => !typeFilter || s.sessionType === typeFilter);
|
|
7054
|
-
|
|
7176
|
+
const sessionsToTerminate = [
|
|
7177
|
+
...inMemoryToTerminate.map((s) => ({ id: s.id.value, type: s.type })),
|
|
7178
|
+
...persistedToTerminate.map((s) => ({ id: s.sessionId, type: s.sessionType }))
|
|
7179
|
+
];
|
|
7180
|
+
if (sessionsToTerminate.length === 0) {
|
|
7055
7181
|
return typeFilter ? `No active ${typeFilter} sessions to terminate.` : "No active sessions to terminate.";
|
|
7056
7182
|
}
|
|
7057
7183
|
const terminated = [];
|
|
7058
7184
|
const errors = [];
|
|
7059
|
-
for (const session of
|
|
7185
|
+
for (const session of sessionsToTerminate) {
|
|
7060
7186
|
try {
|
|
7061
|
-
|
|
7062
|
-
|
|
7187
|
+
const success = await terminateSessionCallback(session.id);
|
|
7188
|
+
if (success) {
|
|
7189
|
+
terminated.push(`${session.type}:${session.id}`);
|
|
7190
|
+
} else {
|
|
7191
|
+
errors.push(`${session.id}: Session not found`);
|
|
7063
7192
|
}
|
|
7064
|
-
await sessionManager.terminateSession(session.id);
|
|
7065
|
-
chatHistoryService?.terminateSession(session.id.value);
|
|
7066
|
-
broadcastTermination(session.id.value);
|
|
7067
|
-
terminated.push(`${session.type}:${session.id.value}`);
|
|
7068
|
-
} catch (error) {
|
|
7069
|
-
errors.push(`${session.id.value}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7070
|
-
}
|
|
7071
|
-
}
|
|
7072
|
-
for (const session of persistedToTerminate) {
|
|
7073
|
-
try {
|
|
7074
|
-
chatHistoryService?.terminateSession(session.sessionId);
|
|
7075
|
-
broadcastTermination(session.sessionId);
|
|
7076
|
-
terminated.push(`${session.sessionType}:${session.sessionId}`);
|
|
7077
7193
|
} catch (error) {
|
|
7078
|
-
errors.push(`${session.
|
|
7194
|
+
errors.push(`${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7079
7195
|
}
|
|
7080
7196
|
}
|
|
7081
7197
|
let result = `Terminated ${terminated.length} session(s)`;
|
|
@@ -7220,6 +7336,23 @@ ${terminatedSessions.map((id) => ` - ${id}`).join("\n")}`;
|
|
|
7220
7336
|
})
|
|
7221
7337
|
}
|
|
7222
7338
|
);
|
|
7339
|
+
const clearContext = tool3(
|
|
7340
|
+
() => {
|
|
7341
|
+
try {
|
|
7342
|
+
if (clearSupervisorContext) {
|
|
7343
|
+
clearSupervisorContext();
|
|
7344
|
+
}
|
|
7345
|
+
return "Supervisor context cleared successfully. Conversation history has been reset.";
|
|
7346
|
+
} catch (error) {
|
|
7347
|
+
return `Error clearing context: ${error instanceof Error ? error.message : String(error)}`;
|
|
7348
|
+
}
|
|
7349
|
+
},
|
|
7350
|
+
{
|
|
7351
|
+
name: "clear_supervisor_context",
|
|
7352
|
+
description: 'Clears the supervisor agent conversation context and history. Use when user asks to "clear context", "reset conversation", "start fresh", or similar requests.',
|
|
7353
|
+
schema: z5.object({})
|
|
7354
|
+
}
|
|
7355
|
+
);
|
|
7223
7356
|
return [
|
|
7224
7357
|
listSessions,
|
|
7225
7358
|
listAvailableAgents,
|
|
@@ -7230,7 +7363,8 @@ ${terminatedSessions.map((id) => ` - ${id}`).join("\n")}`;
|
|
|
7230
7363
|
getSessionInfo,
|
|
7231
7364
|
listSessionsWithWorktrees,
|
|
7232
7365
|
getWorktreeSessionSummary,
|
|
7233
|
-
terminateWorktreeSessions
|
|
7366
|
+
terminateWorktreeSessions,
|
|
7367
|
+
clearContext
|
|
7234
7368
|
];
|
|
7235
7369
|
}
|
|
7236
7370
|
|
|
@@ -7348,6 +7482,8 @@ function formatSize(bytes) {
|
|
|
7348
7482
|
var SupervisorAgent = class extends EventEmitter4 {
|
|
7349
7483
|
logger;
|
|
7350
7484
|
agent;
|
|
7485
|
+
getMessageBroadcaster;
|
|
7486
|
+
getChatHistoryService;
|
|
7351
7487
|
conversationHistory = [];
|
|
7352
7488
|
abortController = null;
|
|
7353
7489
|
isExecuting = false;
|
|
@@ -7359,8 +7495,18 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7359
7495
|
constructor(config2) {
|
|
7360
7496
|
super();
|
|
7361
7497
|
this.logger = config2.logger.child({ component: "SupervisorAgent" });
|
|
7498
|
+
this.getMessageBroadcaster = config2.getMessageBroadcaster;
|
|
7499
|
+
this.getChatHistoryService = config2.getChatHistoryService;
|
|
7362
7500
|
const env = getEnv();
|
|
7363
7501
|
const llm = this.createLLM(env);
|
|
7502
|
+
const terminateSessionCallback = async (sessionId) => {
|
|
7503
|
+
const terminate = config2.getTerminateSession?.();
|
|
7504
|
+
if (!terminate) {
|
|
7505
|
+
this.logger.warn("Terminate session callback not available");
|
|
7506
|
+
return false;
|
|
7507
|
+
}
|
|
7508
|
+
return terminate(sessionId);
|
|
7509
|
+
};
|
|
7364
7510
|
const tools = [
|
|
7365
7511
|
...createWorkspaceTools(config2.workspaceDiscovery),
|
|
7366
7512
|
...createWorktreeTools(config2.workspaceDiscovery, config2.agentSessionManager),
|
|
@@ -7370,7 +7516,9 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7370
7516
|
config2.workspaceDiscovery,
|
|
7371
7517
|
config2.workspacesRoot,
|
|
7372
7518
|
config2.getMessageBroadcaster,
|
|
7373
|
-
config2.getChatHistoryService
|
|
7519
|
+
config2.getChatHistoryService,
|
|
7520
|
+
() => this.clearContext(),
|
|
7521
|
+
terminateSessionCallback
|
|
7374
7522
|
),
|
|
7375
7523
|
...createFilesystemTools(config2.workspacesRoot)
|
|
7376
7524
|
];
|
|
@@ -7621,14 +7769,38 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7621
7769
|
this.logger.debug("Ended command processing");
|
|
7622
7770
|
}
|
|
7623
7771
|
/**
|
|
7624
|
-
* Clears global conversation history.
|
|
7772
|
+
* Clears global conversation history (in-memory only).
|
|
7625
7773
|
* Also resets cancellation state to allow new commands.
|
|
7774
|
+
* @deprecated Use clearContext() for full context clearing with persistence and broadcast.
|
|
7626
7775
|
*/
|
|
7627
7776
|
clearHistory() {
|
|
7628
7777
|
this.conversationHistory = [];
|
|
7629
7778
|
this.isCancelled = false;
|
|
7630
7779
|
this.logger.info("Global conversation history cleared");
|
|
7631
7780
|
}
|
|
7781
|
+
/**
|
|
7782
|
+
* Clears supervisor context completely:
|
|
7783
|
+
* - In-memory conversation history
|
|
7784
|
+
* - Persistent history in database
|
|
7785
|
+
* - Notifies all connected clients
|
|
7786
|
+
*/
|
|
7787
|
+
clearContext() {
|
|
7788
|
+
this.conversationHistory = [];
|
|
7789
|
+
this.isCancelled = false;
|
|
7790
|
+
const chatHistoryService = this.getChatHistoryService?.();
|
|
7791
|
+
if (chatHistoryService) {
|
|
7792
|
+
chatHistoryService.clearSupervisorHistory();
|
|
7793
|
+
}
|
|
7794
|
+
const broadcaster = this.getMessageBroadcaster?.();
|
|
7795
|
+
if (broadcaster) {
|
|
7796
|
+
const clearNotification = JSON.stringify({
|
|
7797
|
+
type: "supervisor.context_cleared",
|
|
7798
|
+
payload: { timestamp: Date.now() }
|
|
7799
|
+
});
|
|
7800
|
+
broadcaster.broadcastToAll(clearNotification);
|
|
7801
|
+
}
|
|
7802
|
+
this.logger.info("Supervisor context cleared (in-memory, persistent, and clients notified)");
|
|
7803
|
+
}
|
|
7632
7804
|
/**
|
|
7633
7805
|
* Resets the cancellation state.
|
|
7634
7806
|
* Call this before starting a new command to ensure previous cancellation doesn't affect it.
|
|
@@ -7864,8 +8036,9 @@ var DEFAULT_FIXTURES_PATH = join7(__dirname, "fixtures");
|
|
|
7864
8036
|
var fixtureCache = /* @__PURE__ */ new Map();
|
|
7865
8037
|
function loadFixture(name, customPath) {
|
|
7866
8038
|
const cacheKey = `${customPath ?? "default"}:${name}`;
|
|
7867
|
-
|
|
7868
|
-
|
|
8039
|
+
const cached = fixtureCache.get(cacheKey);
|
|
8040
|
+
if (cached) {
|
|
8041
|
+
return cached;
|
|
7869
8042
|
}
|
|
7870
8043
|
const fixturesDir = customPath ?? DEFAULT_FIXTURES_PATH;
|
|
7871
8044
|
const filePath = join7(fixturesDir, `${name}.json`);
|
|
@@ -7901,20 +8074,14 @@ async function simulateStreaming(text2, delayMs = DEFAULT_TOKEN_DELAY_MS, onBloc
|
|
|
7901
8074
|
const tokens = tokenize(text2);
|
|
7902
8075
|
let accumulated = "";
|
|
7903
8076
|
for (let i = 0; i < tokens.length; i++) {
|
|
7904
|
-
accumulated += tokens[i];
|
|
7905
|
-
const block =
|
|
7906
|
-
type: "text",
|
|
7907
|
-
text: accumulated
|
|
7908
|
-
};
|
|
8077
|
+
accumulated += tokens[i] ?? "";
|
|
8078
|
+
const block = createTextBlock(accumulated);
|
|
7909
8079
|
onBlock([block], false);
|
|
7910
8080
|
if (i < tokens.length - 1) {
|
|
7911
8081
|
await sleep(delayMs);
|
|
7912
8082
|
}
|
|
7913
8083
|
}
|
|
7914
|
-
const finalBlock =
|
|
7915
|
-
type: "text",
|
|
7916
|
-
text: accumulated
|
|
7917
|
-
};
|
|
8084
|
+
const finalBlock = createTextBlock(accumulated);
|
|
7918
8085
|
onBlock([finalBlock], true);
|
|
7919
8086
|
onComplete();
|
|
7920
8087
|
}
|
|
@@ -7976,7 +8143,7 @@ var MockSupervisorAgent = class extends EventEmitter5 {
|
|
|
7976
8143
|
/**
|
|
7977
8144
|
* Executes a command (non-streaming).
|
|
7978
8145
|
*/
|
|
7979
|
-
|
|
8146
|
+
execute(command, deviceId, _currentSessionId) {
|
|
7980
8147
|
this.logger.info({ command, deviceId }, "Mock supervisor execute");
|
|
7981
8148
|
const response = this.getResponse(command);
|
|
7982
8149
|
this.conversationHistory.push({ role: "user", content: command });
|
|
@@ -8571,8 +8738,10 @@ var TTSService = class {
|
|
|
8571
8738
|
case "elevenlabs":
|
|
8572
8739
|
result = await this.synthesizeElevenLabs(text2);
|
|
8573
8740
|
break;
|
|
8574
|
-
default:
|
|
8575
|
-
|
|
8741
|
+
default: {
|
|
8742
|
+
const exhaustiveCheck = this.config.provider;
|
|
8743
|
+
throw new Error(`Unsupported TTS provider: ${exhaustiveCheck}`);
|
|
8744
|
+
}
|
|
8576
8745
|
}
|
|
8577
8746
|
const elapsed = Date.now() - startTime;
|
|
8578
8747
|
this.logger.info(
|
|
@@ -8905,11 +9074,19 @@ function handleTunnelMessage(rawMessage, tunnelClient, messageBroadcaster, creat
|
|
|
8905
9074
|
return;
|
|
8906
9075
|
}
|
|
8907
9076
|
const handler = handlers[messageType];
|
|
8908
|
-
const
|
|
8909
|
-
if (!
|
|
8910
|
-
logger.warn(
|
|
9077
|
+
const parseResult = parseClientMessageWithErrors(data);
|
|
9078
|
+
if (!parseResult.success) {
|
|
9079
|
+
logger.warn(
|
|
9080
|
+
{
|
|
9081
|
+
type: messageType,
|
|
9082
|
+
errors: parseResult.errors,
|
|
9083
|
+
rawMessage: JSON.stringify(data).slice(0, 500)
|
|
9084
|
+
},
|
|
9085
|
+
"Failed to parse tunnel message - Zod validation failed"
|
|
9086
|
+
);
|
|
8911
9087
|
return;
|
|
8912
9088
|
}
|
|
9089
|
+
const parsedMessage = parseResult.data;
|
|
8913
9090
|
handler(virtualSocket, parsedMessage).catch((error) => {
|
|
8914
9091
|
logger.error(
|
|
8915
9092
|
{ error, type: messageType },
|
|
@@ -9097,6 +9274,29 @@ async function bootstrap() {
|
|
|
9097
9274
|
const pendingSupervisorVoiceCommands = /* @__PURE__ */ new Map();
|
|
9098
9275
|
const pendingAgentVoiceCommands = /* @__PURE__ */ new Map();
|
|
9099
9276
|
const cancelledDuringTranscription = /* @__PURE__ */ new Set();
|
|
9277
|
+
const supervisorMessageAccumulator = {
|
|
9278
|
+
blocks: [],
|
|
9279
|
+
streamingMessageId: null,
|
|
9280
|
+
get() {
|
|
9281
|
+
return this.blocks;
|
|
9282
|
+
},
|
|
9283
|
+
getStreamingMessageId() {
|
|
9284
|
+
return this.streamingMessageId;
|
|
9285
|
+
},
|
|
9286
|
+
set(blocks) {
|
|
9287
|
+
this.blocks = blocks;
|
|
9288
|
+
},
|
|
9289
|
+
clear() {
|
|
9290
|
+
this.blocks = [];
|
|
9291
|
+
this.streamingMessageId = null;
|
|
9292
|
+
},
|
|
9293
|
+
accumulate(newBlocks) {
|
|
9294
|
+
if (this.blocks.length === 0 && newBlocks.length > 0) {
|
|
9295
|
+
this.streamingMessageId = randomUUID5();
|
|
9296
|
+
}
|
|
9297
|
+
accumulateBlocks(this.blocks, newBlocks);
|
|
9298
|
+
}
|
|
9299
|
+
};
|
|
9100
9300
|
const expectedAuthKey = new AuthKey(env.WORKSTATION_AUTH_KEY);
|
|
9101
9301
|
let messageBroadcaster = null;
|
|
9102
9302
|
const supervisorAgent = env.MOCK_MODE ? new MockSupervisorAgent({
|
|
@@ -9109,7 +9309,8 @@ async function bootstrap() {
|
|
|
9109
9309
|
workspacesRoot: env.WORKSPACES_ROOT,
|
|
9110
9310
|
logger,
|
|
9111
9311
|
getMessageBroadcaster: () => messageBroadcaster,
|
|
9112
|
-
getChatHistoryService: () => chatHistoryService
|
|
9312
|
+
getChatHistoryService: () => chatHistoryService,
|
|
9313
|
+
getTerminateSession: () => terminateSession?.terminateAndBroadcast.bind(terminateSession) ?? null
|
|
9113
9314
|
});
|
|
9114
9315
|
if (env.MOCK_MODE) {
|
|
9115
9316
|
logger.info("Mock Supervisor Agent initialized for screenshot automation");
|
|
@@ -9188,11 +9389,10 @@ async function bootstrap() {
|
|
|
9188
9389
|
const syncMessage = message;
|
|
9189
9390
|
const client = clientRegistry.getBySocket(socket) ?? (syncMessage.device_id ? clientRegistry.getByDeviceId(new DeviceId(syncMessage.device_id)) : void 0);
|
|
9190
9391
|
const subscriptions2 = client ? client.getSubscriptions() : [];
|
|
9191
|
-
const isLightweight = syncMessage.lightweight === true;
|
|
9192
9392
|
const inMemorySessions = sessionManager.getSessionInfos();
|
|
9193
9393
|
const persistedAgentSessions = chatHistoryService.getActiveAgentSessions();
|
|
9194
9394
|
logger.debug(
|
|
9195
|
-
{ persistedAgentSessions, inMemoryCount: inMemorySessions.length
|
|
9395
|
+
{ persistedAgentSessions, inMemoryCount: inMemorySessions.length },
|
|
9196
9396
|
"Sync: fetched sessions"
|
|
9197
9397
|
);
|
|
9198
9398
|
const inMemorySessionIds = new Set(
|
|
@@ -9217,119 +9417,14 @@ async function bootstrap() {
|
|
|
9217
9417
|
};
|
|
9218
9418
|
});
|
|
9219
9419
|
const sessions2 = [...inMemorySessions, ...restoredAgentSessions];
|
|
9220
|
-
if (isLightweight) {
|
|
9221
|
-
const availableAgentsMap2 = getAvailableAgents();
|
|
9222
|
-
const availableAgents2 = Array.from(availableAgentsMap2.values()).map(
|
|
9223
|
-
(agent) => ({
|
|
9224
|
-
name: agent.name,
|
|
9225
|
-
base_type: agent.baseType,
|
|
9226
|
-
description: agent.description,
|
|
9227
|
-
is_alias: agent.isAlias
|
|
9228
|
-
})
|
|
9229
|
-
);
|
|
9230
|
-
const hiddenBaseTypes2 = getDisabledBaseAgents();
|
|
9231
|
-
const workspacesList2 = await workspaceDiscovery.listWorkspaces();
|
|
9232
|
-
const workspaces2 = await Promise.all(
|
|
9233
|
-
workspacesList2.map(async (ws) => {
|
|
9234
|
-
const projects = await workspaceDiscovery.listProjects(ws.name);
|
|
9235
|
-
return {
|
|
9236
|
-
name: ws.name,
|
|
9237
|
-
projects: projects.map((p) => ({
|
|
9238
|
-
name: p.name,
|
|
9239
|
-
is_git_repo: p.isGitRepo,
|
|
9240
|
-
default_branch: p.defaultBranch
|
|
9241
|
-
}))
|
|
9242
|
-
};
|
|
9243
|
-
})
|
|
9244
|
-
);
|
|
9245
|
-
const executingStates2 = {};
|
|
9246
|
-
for (const session of sessions2) {
|
|
9247
|
-
if (session.session_type === "cursor" || session.session_type === "claude" || session.session_type === "opencode") {
|
|
9248
|
-
executingStates2[session.session_id] = agentSessionManager.isExecuting(
|
|
9249
|
-
session.session_id
|
|
9250
|
-
);
|
|
9251
|
-
}
|
|
9252
|
-
}
|
|
9253
|
-
const supervisorIsExecuting2 = supervisorAgent.isProcessing();
|
|
9254
|
-
logger.info(
|
|
9255
|
-
{
|
|
9256
|
-
totalSessions: sessions2.length,
|
|
9257
|
-
isLightweight: true,
|
|
9258
|
-
availableAgentsCount: availableAgents2.length,
|
|
9259
|
-
workspacesCount: workspaces2.length,
|
|
9260
|
-
supervisorIsExecuting: supervisorIsExecuting2
|
|
9261
|
-
},
|
|
9262
|
-
"Sync: sending lightweight state to client (no histories)"
|
|
9263
|
-
);
|
|
9264
|
-
const syncStateMessage2 = JSON.stringify({
|
|
9265
|
-
type: "sync.state",
|
|
9266
|
-
id: syncMessage.id,
|
|
9267
|
-
payload: {
|
|
9268
|
-
sessions: sessions2,
|
|
9269
|
-
subscriptions: subscriptions2,
|
|
9270
|
-
availableAgents: availableAgents2,
|
|
9271
|
-
hiddenBaseTypes: hiddenBaseTypes2,
|
|
9272
|
-
workspaces: workspaces2,
|
|
9273
|
-
supervisorIsExecuting: supervisorIsExecuting2,
|
|
9274
|
-
executingStates: executingStates2
|
|
9275
|
-
// Omit: supervisorHistory, agentHistories, currentStreamingBlocks
|
|
9276
|
-
}
|
|
9277
|
-
});
|
|
9278
|
-
sendToDevice(socket, syncMessage.device_id, syncStateMessage2);
|
|
9279
|
-
return Promise.resolve();
|
|
9280
|
-
}
|
|
9281
9420
|
const supervisorHistoryRaw = chatHistoryService.getSupervisorHistory();
|
|
9282
|
-
|
|
9283
|
-
supervisorHistoryRaw.
|
|
9284
|
-
sequence: msg.sequence,
|
|
9285
|
-
role: msg.role,
|
|
9286
|
-
content: msg.content,
|
|
9287
|
-
content_blocks: await chatHistoryService.enrichBlocksWithAudio(
|
|
9288
|
-
msg.contentBlocks,
|
|
9289
|
-
msg.audioOutputPath,
|
|
9290
|
-
msg.audioInputPath,
|
|
9291
|
-
false
|
|
9292
|
-
// Don't include audio in sync.state
|
|
9293
|
-
),
|
|
9294
|
-
createdAt: msg.createdAt.toISOString()
|
|
9295
|
-
}))
|
|
9296
|
-
);
|
|
9297
|
-
if (supervisorHistory.length > 0) {
|
|
9298
|
-
const historyForAgent = supervisorHistory.filter((msg) => msg.role === "user" || msg.role === "assistant").map((msg) => ({
|
|
9421
|
+
if (supervisorHistoryRaw.length > 0) {
|
|
9422
|
+
const historyForAgent = supervisorHistoryRaw.filter((msg) => msg.role === "user" || msg.role === "assistant").map((msg) => ({
|
|
9299
9423
|
role: msg.role,
|
|
9300
9424
|
content: msg.content
|
|
9301
9425
|
}));
|
|
9302
9426
|
supervisorAgent.restoreHistory(historyForAgent);
|
|
9303
9427
|
}
|
|
9304
|
-
const agentSessionIds = sessions2.filter(
|
|
9305
|
-
(s) => s.session_type === "cursor" || s.session_type === "claude" || s.session_type === "opencode"
|
|
9306
|
-
).map((s) => s.session_id);
|
|
9307
|
-
const agentHistoriesMap = chatHistoryService.getAllAgentHistories(agentSessionIds);
|
|
9308
|
-
const agentHistories = {};
|
|
9309
|
-
const historyEntries = Array.from(agentHistoriesMap.entries());
|
|
9310
|
-
const processedHistories = await Promise.all(
|
|
9311
|
-
historyEntries.map(async ([sessionId, history]) => {
|
|
9312
|
-
const enrichedHistory = await Promise.all(
|
|
9313
|
-
history.map(async (msg) => ({
|
|
9314
|
-
sequence: msg.sequence,
|
|
9315
|
-
role: msg.role,
|
|
9316
|
-
content: msg.content,
|
|
9317
|
-
content_blocks: await chatHistoryService.enrichBlocksWithAudio(
|
|
9318
|
-
msg.contentBlocks,
|
|
9319
|
-
msg.audioOutputPath,
|
|
9320
|
-
msg.audioInputPath,
|
|
9321
|
-
false
|
|
9322
|
-
// Don't include audio in sync.state
|
|
9323
|
-
),
|
|
9324
|
-
createdAt: msg.createdAt.toISOString()
|
|
9325
|
-
}))
|
|
9326
|
-
);
|
|
9327
|
-
return { sessionId, history: enrichedHistory };
|
|
9328
|
-
})
|
|
9329
|
-
);
|
|
9330
|
-
for (const { sessionId, history } of processedHistories) {
|
|
9331
|
-
agentHistories[sessionId] = history;
|
|
9332
|
-
}
|
|
9333
9428
|
const availableAgentsMap = getAvailableAgents();
|
|
9334
9429
|
const availableAgents = Array.from(availableAgentsMap.values()).map(
|
|
9335
9430
|
(agent) => ({
|
|
@@ -9362,31 +9457,23 @@ async function bootstrap() {
|
|
|
9362
9457
|
);
|
|
9363
9458
|
}
|
|
9364
9459
|
}
|
|
9365
|
-
const
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
}
|
|
9460
|
+
const supervisorIsExecuting = supervisorAgent.isProcessing();
|
|
9461
|
+
let currentStreamingBlocks;
|
|
9462
|
+
if (supervisorIsExecuting) {
|
|
9463
|
+
const blocks = supervisorMessageAccumulator.get();
|
|
9464
|
+
if (blocks && blocks.length > 0) {
|
|
9465
|
+
currentStreamingBlocks = blocks;
|
|
9372
9466
|
}
|
|
9373
9467
|
}
|
|
9374
|
-
const supervisorIsExecuting = supervisorAgent.isProcessing();
|
|
9375
9468
|
logger.info(
|
|
9376
9469
|
{
|
|
9377
9470
|
totalSessions: sessions2.length,
|
|
9378
|
-
sessionTypes: sessions2.map((s) => ({
|
|
9379
|
-
id: s.session_id,
|
|
9380
|
-
type: s.session_type
|
|
9381
|
-
})),
|
|
9382
|
-
agentHistoriesCount: Object.keys(agentHistories).length,
|
|
9383
9471
|
availableAgentsCount: availableAgents.length,
|
|
9384
9472
|
workspacesCount: workspaces.length,
|
|
9385
9473
|
supervisorIsExecuting,
|
|
9386
|
-
|
|
9387
|
-
streamingBlocksCount: Object.keys(currentStreamingBlocks).length
|
|
9474
|
+
hasStreamingBlocks: !!currentStreamingBlocks
|
|
9388
9475
|
},
|
|
9389
|
-
"Sync: sending state to client"
|
|
9476
|
+
"Sync: sending state to client (v1.13 - no histories)"
|
|
9390
9477
|
);
|
|
9391
9478
|
const syncStateMessage = JSON.stringify({
|
|
9392
9479
|
type: "sync.state",
|
|
@@ -9394,14 +9481,15 @@ async function bootstrap() {
|
|
|
9394
9481
|
payload: {
|
|
9395
9482
|
sessions: sessions2,
|
|
9396
9483
|
subscriptions: subscriptions2,
|
|
9397
|
-
supervisorHistory,
|
|
9398
|
-
agentHistories,
|
|
9399
9484
|
availableAgents,
|
|
9400
9485
|
hiddenBaseTypes,
|
|
9401
9486
|
workspaces,
|
|
9402
9487
|
supervisorIsExecuting,
|
|
9403
9488
|
executingStates,
|
|
9404
|
-
|
|
9489
|
+
// Only include supervisor streaming blocks for mid-stream join
|
|
9490
|
+
currentStreamingBlocks
|
|
9491
|
+
// Protocol v1.13: No supervisorHistory, agentHistories
|
|
9492
|
+
// Clients use history.request for on-demand loading
|
|
9405
9493
|
}
|
|
9406
9494
|
});
|
|
9407
9495
|
sendToDevice(socket, syncMessage.device_id, syncStateMessage);
|
|
@@ -9463,7 +9551,7 @@ async function bootstrap() {
|
|
|
9463
9551
|
let commandText;
|
|
9464
9552
|
const messageId = commandMessage.payload.message_id;
|
|
9465
9553
|
if (messageBroadcaster) {
|
|
9466
|
-
const ackMessageId = messageId
|
|
9554
|
+
const ackMessageId = messageId ?? commandMessage.id;
|
|
9467
9555
|
const ackMessage = {
|
|
9468
9556
|
type: "message.ack",
|
|
9469
9557
|
payload: {
|
|
@@ -9496,7 +9584,7 @@ async function bootstrap() {
|
|
|
9496
9584
|
const errorEvent = {
|
|
9497
9585
|
type: "supervisor.transcription",
|
|
9498
9586
|
payload: {
|
|
9499
|
-
|
|
9587
|
+
transcription: "",
|
|
9500
9588
|
error: "Voice transcription not available - STT service not configured",
|
|
9501
9589
|
message_id: messageId,
|
|
9502
9590
|
timestamp: Date.now()
|
|
@@ -9546,7 +9634,7 @@ async function bootstrap() {
|
|
|
9546
9634
|
const transcriptionEvent = {
|
|
9547
9635
|
type: "supervisor.transcription",
|
|
9548
9636
|
payload: {
|
|
9549
|
-
|
|
9637
|
+
transcription: commandText,
|
|
9550
9638
|
language: transcriptionResult.language,
|
|
9551
9639
|
duration: transcriptionResult.duration,
|
|
9552
9640
|
message_id: messageId,
|
|
@@ -9575,7 +9663,7 @@ async function bootstrap() {
|
|
|
9575
9663
|
const errorEvent = {
|
|
9576
9664
|
type: "supervisor.transcription",
|
|
9577
9665
|
payload: {
|
|
9578
|
-
|
|
9666
|
+
transcription: "",
|
|
9579
9667
|
error: error instanceof Error ? error.message : "Transcription failed",
|
|
9580
9668
|
message_id: messageId,
|
|
9581
9669
|
timestamp: Date.now(),
|
|
@@ -9721,13 +9809,7 @@ async function bootstrap() {
|
|
|
9721
9809
|
const tunnelClient2 = clearMessage.device_id ? clientRegistry.getByDeviceId(new DeviceId(clearMessage.device_id)) : void 0;
|
|
9722
9810
|
const isAuthenticated = directClient?.isAuthenticated ?? tunnelClient2?.isAuthenticated;
|
|
9723
9811
|
if (isAuthenticated) {
|
|
9724
|
-
supervisorAgent.
|
|
9725
|
-
chatHistoryService.clearSupervisorHistory();
|
|
9726
|
-
const clearNotification = JSON.stringify({
|
|
9727
|
-
type: "supervisor.context_cleared",
|
|
9728
|
-
payload: { timestamp: Date.now() }
|
|
9729
|
-
});
|
|
9730
|
-
broadcaster.broadcastToAll(clearNotification);
|
|
9812
|
+
supervisorAgent.clearContext();
|
|
9731
9813
|
sendToDevice(
|
|
9732
9814
|
socket,
|
|
9733
9815
|
clearMessage.device_id,
|
|
@@ -9855,45 +9937,29 @@ async function bootstrap() {
|
|
|
9855
9937
|
},
|
|
9856
9938
|
"Client subscribed to agent session"
|
|
9857
9939
|
);
|
|
9858
|
-
const history = chatHistoryService.getAgentHistory(sessionId, 50);
|
|
9859
|
-
const enrichedHistory = await Promise.all(
|
|
9860
|
-
history.map(async (msg) => ({
|
|
9861
|
-
id: msg.id,
|
|
9862
|
-
sequence: msg.sequence,
|
|
9863
|
-
role: msg.role,
|
|
9864
|
-
content: msg.content,
|
|
9865
|
-
content_blocks: await chatHistoryService.enrichBlocksWithAudio(
|
|
9866
|
-
msg.contentBlocks,
|
|
9867
|
-
msg.audioOutputPath,
|
|
9868
|
-
msg.audioInputPath,
|
|
9869
|
-
false
|
|
9870
|
-
// Don't include audio in subscription response
|
|
9871
|
-
),
|
|
9872
|
-
createdAt: msg.createdAt.toISOString()
|
|
9873
|
-
}))
|
|
9874
|
-
);
|
|
9875
9940
|
const isExecuting = agentSessionManager.isExecuting(sessionId);
|
|
9876
9941
|
const currentStreamingBlocks = agentMessageAccumulator.get(sessionId) ?? [];
|
|
9942
|
+
const streamingMessageId = currentStreamingBlocks.length > 0 ? agentStreamingMessageIds.get(sessionId) : void 0;
|
|
9877
9943
|
sendToDevice(
|
|
9878
9944
|
socket,
|
|
9879
9945
|
subscribeMessage.device_id,
|
|
9880
9946
|
JSON.stringify({
|
|
9881
9947
|
type: "session.subscribed",
|
|
9882
9948
|
session_id: sessionId,
|
|
9883
|
-
history: enrichedHistory,
|
|
9884
9949
|
is_executing: isExecuting,
|
|
9885
|
-
current_streaming_blocks: currentStreamingBlocks.length > 0 ? currentStreamingBlocks : void 0
|
|
9950
|
+
current_streaming_blocks: currentStreamingBlocks.length > 0 ? currentStreamingBlocks : void 0,
|
|
9951
|
+
streaming_message_id: streamingMessageId
|
|
9886
9952
|
})
|
|
9887
9953
|
);
|
|
9888
9954
|
logger.debug(
|
|
9889
9955
|
{
|
|
9890
9956
|
deviceId: client.deviceId.value,
|
|
9891
9957
|
sessionId,
|
|
9892
|
-
historyCount: enrichedHistory.length,
|
|
9893
9958
|
isExecuting,
|
|
9894
|
-
streamingBlocksCount: currentStreamingBlocks.length
|
|
9959
|
+
streamingBlocksCount: currentStreamingBlocks.length,
|
|
9960
|
+
streamingMessageId
|
|
9895
9961
|
},
|
|
9896
|
-
"
|
|
9962
|
+
"Agent session subscribed (v1.13 - use history.request for messages)"
|
|
9897
9963
|
);
|
|
9898
9964
|
} else {
|
|
9899
9965
|
const result = subscriptionService.subscribe(
|
|
@@ -9950,7 +10016,7 @@ async function bootstrap() {
|
|
|
9950
10016
|
const sessionId = execMessage.session_id;
|
|
9951
10017
|
const messageId = execMessage.payload.message_id;
|
|
9952
10018
|
if (deviceId && messageBroadcaster) {
|
|
9953
|
-
const ackMessageId = messageId
|
|
10019
|
+
const ackMessageId = messageId ?? execMessage.id;
|
|
9954
10020
|
const ackMessage = {
|
|
9955
10021
|
type: "message.ack",
|
|
9956
10022
|
payload: {
|
|
@@ -10022,7 +10088,7 @@ async function bootstrap() {
|
|
|
10022
10088
|
type: "session.transcription",
|
|
10023
10089
|
session_id: sessionId,
|
|
10024
10090
|
payload: {
|
|
10025
|
-
|
|
10091
|
+
transcription: transcribedText,
|
|
10026
10092
|
language: transcriptionResult.language,
|
|
10027
10093
|
duration: transcriptionResult.duration,
|
|
10028
10094
|
message_id: messageId,
|
|
@@ -10115,7 +10181,7 @@ async function bootstrap() {
|
|
|
10115
10181
|
type: "session.transcription",
|
|
10116
10182
|
session_id: sessionId,
|
|
10117
10183
|
payload: {
|
|
10118
|
-
|
|
10184
|
+
transcription: "",
|
|
10119
10185
|
error: error instanceof Error ? error.message : "Transcription failed",
|
|
10120
10186
|
message_id: messageId,
|
|
10121
10187
|
timestamp: Date.now()
|
|
@@ -10441,18 +10507,22 @@ async function bootstrap() {
|
|
|
10441
10507
|
"history.request": async (socket, message) => {
|
|
10442
10508
|
const historyRequest = message;
|
|
10443
10509
|
const sessionId = historyRequest.payload?.session_id;
|
|
10510
|
+
const beforeSequence = historyRequest.payload?.before_sequence;
|
|
10511
|
+
const limit = historyRequest.payload?.limit;
|
|
10444
10512
|
const isSupervisor = !sessionId;
|
|
10445
10513
|
logger.debug(
|
|
10446
|
-
{ sessionId, isSupervisor, requestId: historyRequest.id },
|
|
10447
|
-
"
|
|
10514
|
+
{ sessionId, isSupervisor, beforeSequence, limit, requestId: historyRequest.id },
|
|
10515
|
+
"Paginated history request received"
|
|
10448
10516
|
);
|
|
10449
10517
|
try {
|
|
10450
10518
|
if (isSupervisor) {
|
|
10451
|
-
const
|
|
10519
|
+
const result = chatHistoryService.getSupervisorHistoryPaginated({
|
|
10520
|
+
beforeSequence,
|
|
10521
|
+
limit
|
|
10522
|
+
});
|
|
10452
10523
|
const supervisorHistory = await Promise.all(
|
|
10453
|
-
|
|
10524
|
+
result.messages.map(async (msg) => ({
|
|
10454
10525
|
message_id: msg.id,
|
|
10455
|
-
// Include message ID for audio.request
|
|
10456
10526
|
sequence: msg.sequence,
|
|
10457
10527
|
role: msg.role,
|
|
10458
10528
|
content: msg.content,
|
|
@@ -10461,32 +10531,54 @@ async function bootstrap() {
|
|
|
10461
10531
|
msg.audioOutputPath,
|
|
10462
10532
|
msg.audioInputPath,
|
|
10463
10533
|
false
|
|
10464
|
-
// Don't include audio in history response
|
|
10465
10534
|
),
|
|
10466
10535
|
createdAt: msg.createdAt.toISOString()
|
|
10467
10536
|
}))
|
|
10468
10537
|
);
|
|
10469
10538
|
const isExecuting = supervisorAgent.isProcessing();
|
|
10539
|
+
let currentStreamingBlocks;
|
|
10540
|
+
let streamingMessageId;
|
|
10541
|
+
if (isExecuting && !beforeSequence) {
|
|
10542
|
+
const blocks = supervisorMessageAccumulator.get();
|
|
10543
|
+
if (blocks && blocks.length > 0) {
|
|
10544
|
+
currentStreamingBlocks = blocks;
|
|
10545
|
+
streamingMessageId = supervisorMessageAccumulator.getStreamingMessageId() ?? void 0;
|
|
10546
|
+
}
|
|
10547
|
+
}
|
|
10470
10548
|
socket.send(
|
|
10471
10549
|
JSON.stringify({
|
|
10472
10550
|
type: "history.response",
|
|
10473
10551
|
id: historyRequest.id,
|
|
10474
10552
|
payload: {
|
|
10475
10553
|
session_id: null,
|
|
10476
|
-
// Indicates supervisor
|
|
10477
10554
|
history: supervisorHistory,
|
|
10478
|
-
|
|
10555
|
+
has_more: result.hasMore,
|
|
10556
|
+
oldest_sequence: result.oldestSequence,
|
|
10557
|
+
newest_sequence: result.newestSequence,
|
|
10558
|
+
is_executing: isExecuting,
|
|
10559
|
+
current_streaming_blocks: currentStreamingBlocks,
|
|
10560
|
+
streaming_message_id: streamingMessageId
|
|
10479
10561
|
}
|
|
10480
10562
|
})
|
|
10481
10563
|
);
|
|
10482
10564
|
logger.debug(
|
|
10483
|
-
{
|
|
10484
|
-
|
|
10565
|
+
{
|
|
10566
|
+
messageCount: supervisorHistory.length,
|
|
10567
|
+
hasMore: result.hasMore,
|
|
10568
|
+
oldestSeq: result.oldestSequence,
|
|
10569
|
+
newestSeq: result.newestSequence,
|
|
10570
|
+
isExecuting
|
|
10571
|
+
},
|
|
10572
|
+
"Paginated supervisor history sent"
|
|
10485
10573
|
);
|
|
10486
10574
|
} else {
|
|
10487
|
-
const
|
|
10575
|
+
const result = chatHistoryService.getAgentHistoryPaginated(sessionId, {
|
|
10576
|
+
beforeSequence,
|
|
10577
|
+
limit
|
|
10578
|
+
});
|
|
10488
10579
|
const enrichedHistory = await Promise.all(
|
|
10489
|
-
|
|
10580
|
+
result.messages.map(async (msg) => ({
|
|
10581
|
+
message_id: msg.id,
|
|
10490
10582
|
sequence: msg.sequence,
|
|
10491
10583
|
role: msg.role,
|
|
10492
10584
|
content: msg.content,
|
|
@@ -10495,17 +10587,18 @@ async function bootstrap() {
|
|
|
10495
10587
|
msg.audioOutputPath,
|
|
10496
10588
|
msg.audioInputPath,
|
|
10497
10589
|
false
|
|
10498
|
-
// Don't include audio in history response
|
|
10499
10590
|
),
|
|
10500
10591
|
createdAt: msg.createdAt.toISOString()
|
|
10501
10592
|
}))
|
|
10502
10593
|
);
|
|
10503
10594
|
const isExecuting = agentSessionManager.isExecuting(sessionId);
|
|
10504
10595
|
let currentStreamingBlocks;
|
|
10505
|
-
|
|
10596
|
+
let streamingMessageId;
|
|
10597
|
+
if (isExecuting && !beforeSequence) {
|
|
10506
10598
|
const blocks = agentMessageAccumulator.get(sessionId);
|
|
10507
10599
|
if (blocks && blocks.length > 0) {
|
|
10508
10600
|
currentStreamingBlocks = blocks;
|
|
10601
|
+
streamingMessageId = agentStreamingMessageIds.get(sessionId);
|
|
10509
10602
|
}
|
|
10510
10603
|
}
|
|
10511
10604
|
socket.send(
|
|
@@ -10515,13 +10608,17 @@ async function bootstrap() {
|
|
|
10515
10608
|
payload: {
|
|
10516
10609
|
session_id: sessionId,
|
|
10517
10610
|
history: enrichedHistory,
|
|
10611
|
+
has_more: result.hasMore,
|
|
10612
|
+
oldest_sequence: result.oldestSequence,
|
|
10613
|
+
newest_sequence: result.newestSequence,
|
|
10518
10614
|
is_executing: isExecuting,
|
|
10519
|
-
current_streaming_blocks: currentStreamingBlocks
|
|
10615
|
+
current_streaming_blocks: currentStreamingBlocks,
|
|
10616
|
+
streaming_message_id: streamingMessageId
|
|
10520
10617
|
}
|
|
10521
10618
|
})
|
|
10522
10619
|
);
|
|
10523
10620
|
logger.debug(
|
|
10524
|
-
{ sessionId, messageCount: enrichedHistory.length, isExecuting },
|
|
10621
|
+
{ sessionId, messageCount: enrichedHistory.length, isExecuting, streamingMessageId },
|
|
10525
10622
|
"Agent session history sent"
|
|
10526
10623
|
);
|
|
10527
10624
|
}
|
|
@@ -10555,7 +10652,7 @@ async function bootstrap() {
|
|
|
10555
10652
|
id: audioRequest.id,
|
|
10556
10653
|
payload: {
|
|
10557
10654
|
message_id,
|
|
10558
|
-
|
|
10655
|
+
audio_base64: audioBase64
|
|
10559
10656
|
}
|
|
10560
10657
|
})
|
|
10561
10658
|
);
|
|
@@ -10700,6 +10797,7 @@ async function bootstrap() {
|
|
|
10700
10797
|
});
|
|
10701
10798
|
terminateSession = new TerminateSessionUseCase({
|
|
10702
10799
|
sessionManager,
|
|
10800
|
+
agentSessionManager,
|
|
10703
10801
|
messageBroadcaster,
|
|
10704
10802
|
chatHistoryService,
|
|
10705
10803
|
logger
|
|
@@ -10712,6 +10810,18 @@ async function bootstrap() {
|
|
|
10712
10810
|
});
|
|
10713
10811
|
const broadcaster = messageBroadcaster;
|
|
10714
10812
|
const agentMessageAccumulator = /* @__PURE__ */ new Map();
|
|
10813
|
+
const agentStreamingMessageIds = /* @__PURE__ */ new Map();
|
|
10814
|
+
const getOrCreateAgentStreamingMessageId = (sessionId) => {
|
|
10815
|
+
let messageId = agentStreamingMessageIds.get(sessionId);
|
|
10816
|
+
if (!messageId) {
|
|
10817
|
+
messageId = randomUUID5();
|
|
10818
|
+
agentStreamingMessageIds.set(sessionId, messageId);
|
|
10819
|
+
}
|
|
10820
|
+
return messageId;
|
|
10821
|
+
};
|
|
10822
|
+
const clearAgentStreamingMessageId = (sessionId) => {
|
|
10823
|
+
agentStreamingMessageIds.delete(sessionId);
|
|
10824
|
+
};
|
|
10715
10825
|
agentSessionManager.on(
|
|
10716
10826
|
"blocks",
|
|
10717
10827
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
@@ -10784,9 +10894,11 @@ async function bootstrap() {
|
|
|
10784
10894
|
const accumulatedBlocks = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10785
10895
|
const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
|
|
10786
10896
|
const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
10897
|
+
const streamingMessageId = getOrCreateAgentStreamingMessageId(sessionId);
|
|
10787
10898
|
const outputEvent = {
|
|
10788
10899
|
type: "session.output",
|
|
10789
10900
|
session_id: sessionId,
|
|
10901
|
+
streaming_message_id: streamingMessageId,
|
|
10790
10902
|
payload: {
|
|
10791
10903
|
content_type: "agent",
|
|
10792
10904
|
content: fullAccumulatedText,
|
|
@@ -10801,6 +10913,9 @@ async function bootstrap() {
|
|
|
10801
10913
|
sessionId,
|
|
10802
10914
|
JSON.stringify(outputEvent)
|
|
10803
10915
|
);
|
|
10916
|
+
if (isComplete) {
|
|
10917
|
+
clearAgentStreamingMessageId(sessionId);
|
|
10918
|
+
}
|
|
10804
10919
|
if (isComplete && fullTextContent.length > 0) {
|
|
10805
10920
|
const pendingVoiceCommand = pendingAgentVoiceCommands.get(sessionId);
|
|
10806
10921
|
if (pendingVoiceCommand && ttsService) {
|
|
@@ -10850,7 +10965,7 @@ async function bootstrap() {
|
|
|
10850
10965
|
session_id: sessionId,
|
|
10851
10966
|
payload: {
|
|
10852
10967
|
text: textForTTS,
|
|
10853
|
-
|
|
10968
|
+
audio_base64: audioBase64,
|
|
10854
10969
|
audio_format: "mp3",
|
|
10855
10970
|
duration: ttsResult.duration,
|
|
10856
10971
|
message_id: pendingMessageId,
|
|
@@ -10876,7 +10991,6 @@ async function bootstrap() {
|
|
|
10876
10991
|
}
|
|
10877
10992
|
}
|
|
10878
10993
|
);
|
|
10879
|
-
let supervisorBlockAccumulator = [];
|
|
10880
10994
|
supervisorAgent.on(
|
|
10881
10995
|
"blocks",
|
|
10882
10996
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
@@ -10891,17 +11005,19 @@ async function bootstrap() {
|
|
|
10891
11005
|
{ deviceId, blockCount: blocks.length, isComplete },
|
|
10892
11006
|
"Ignoring supervisor blocks - execution was cancelled"
|
|
10893
11007
|
);
|
|
10894
|
-
|
|
11008
|
+
supervisorMessageAccumulator.clear();
|
|
10895
11009
|
return;
|
|
10896
11010
|
}
|
|
10897
11011
|
const persistableBlocks = blocks.filter((b) => b.block_type !== "status");
|
|
10898
11012
|
if (persistableBlocks.length > 0) {
|
|
10899
|
-
|
|
11013
|
+
supervisorMessageAccumulator.accumulate(persistableBlocks);
|
|
10900
11014
|
}
|
|
10901
|
-
const mergedBlocks = mergeToolBlocks(
|
|
11015
|
+
const mergedBlocks = mergeToolBlocks(supervisorMessageAccumulator.get());
|
|
10902
11016
|
const textContent = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
11017
|
+
const streamingMessageId = supervisorMessageAccumulator.getStreamingMessageId();
|
|
10903
11018
|
const outputEvent = {
|
|
10904
11019
|
type: "supervisor.output",
|
|
11020
|
+
streaming_message_id: streamingMessageId,
|
|
10905
11021
|
payload: {
|
|
10906
11022
|
content_type: "supervisor",
|
|
10907
11023
|
content: textContent,
|
|
@@ -10913,7 +11029,7 @@ async function bootstrap() {
|
|
|
10913
11029
|
const message = JSON.stringify(outputEvent);
|
|
10914
11030
|
broadcaster.broadcastToAll(message);
|
|
10915
11031
|
if (isComplete) {
|
|
10916
|
-
|
|
11032
|
+
supervisorMessageAccumulator.clear();
|
|
10917
11033
|
}
|
|
10918
11034
|
if (isComplete && finalOutput && finalOutput.length > 0) {
|
|
10919
11035
|
chatHistoryService.saveSupervisorMessage(
|
|
@@ -10963,7 +11079,7 @@ async function bootstrap() {
|
|
|
10963
11079
|
type: "supervisor.voice_output",
|
|
10964
11080
|
payload: {
|
|
10965
11081
|
text: textForTTS,
|
|
10966
|
-
|
|
11082
|
+
audio_base64: audioBase64,
|
|
10967
11083
|
audio_format: "mp3",
|
|
10968
11084
|
duration: ttsResult.duration,
|
|
10969
11085
|
message_id: pendingMessageId,
|
|
@@ -11123,23 +11239,52 @@ async function bootstrap() {
|
|
|
11123
11239
|
} catch (error) {
|
|
11124
11240
|
logger.error({ error }, "Failed to connect to tunnel (will retry)");
|
|
11125
11241
|
}
|
|
11242
|
+
const SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
11243
|
+
let isShuttingDown = false;
|
|
11126
11244
|
const shutdown = async (signal) => {
|
|
11245
|
+
if (isShuttingDown) {
|
|
11246
|
+
logger.warn({ signal }, "Shutdown already in progress, forcing exit");
|
|
11247
|
+
process.exit(1);
|
|
11248
|
+
}
|
|
11249
|
+
isShuttingDown = true;
|
|
11127
11250
|
logger.info({ signal }, "Shutdown signal received");
|
|
11251
|
+
const forceExitTimeout = setTimeout(() => {
|
|
11252
|
+
logger.error("Graceful shutdown timeout exceeded, forcing exit");
|
|
11253
|
+
process.exit(1);
|
|
11254
|
+
}, SHUTDOWN_TIMEOUT_MS);
|
|
11128
11255
|
try {
|
|
11129
11256
|
logger.info("Disconnecting from tunnel...");
|
|
11130
11257
|
tunnelClient.disconnect();
|
|
11131
11258
|
logger.info("Cleaning up agent sessions...");
|
|
11132
11259
|
agentSessionManager.cleanup();
|
|
11133
11260
|
logger.info("Terminating all sessions...");
|
|
11134
|
-
|
|
11135
|
-
|
|
11261
|
+
const terminatePromise = sessionManager.terminateAll();
|
|
11262
|
+
const sessionTimeoutPromise = new Promise((_, reject) => {
|
|
11263
|
+
setTimeout(() => reject(new Error("Session termination timeout")), 5e3);
|
|
11264
|
+
});
|
|
11265
|
+
try {
|
|
11266
|
+
await Promise.race([terminatePromise, sessionTimeoutPromise]);
|
|
11267
|
+
logger.info("All sessions terminated");
|
|
11268
|
+
} catch (error) {
|
|
11269
|
+
logger.warn({ error }, "Session termination timed out, continuing shutdown");
|
|
11270
|
+
}
|
|
11136
11271
|
logger.info("Closing HTTP server...");
|
|
11137
|
-
|
|
11272
|
+
const closePromise = app.close();
|
|
11273
|
+
const closeTimeoutPromise = new Promise((_, reject) => {
|
|
11274
|
+
setTimeout(() => reject(new Error("HTTP server close timeout")), 2e3);
|
|
11275
|
+
});
|
|
11276
|
+
try {
|
|
11277
|
+
await Promise.race([closePromise, closeTimeoutPromise]);
|
|
11278
|
+
} catch (error) {
|
|
11279
|
+
logger.warn({ error }, "HTTP server close timed out, continuing shutdown");
|
|
11280
|
+
}
|
|
11138
11281
|
logger.info("Closing database...");
|
|
11139
11282
|
closeDatabase();
|
|
11283
|
+
clearTimeout(forceExitTimeout);
|
|
11140
11284
|
logger.info("Shutdown complete");
|
|
11141
11285
|
process.exit(0);
|
|
11142
11286
|
} catch (error) {
|
|
11287
|
+
clearTimeout(forceExitTimeout);
|
|
11143
11288
|
logger.error({ error }, "Error during shutdown");
|
|
11144
11289
|
process.exit(1);
|
|
11145
11290
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiflis-io/tiflis-code-workstation",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "Workstation server for tiflis-code - manages agent sessions and terminal access",
|
|
5
5
|
"author": "Roman Barinov <rbarinov@gmail.com>",
|
|
6
6
|
"license": "FSL-1.1-NC",
|