@tiflis-io/tiflis-code-workstation 0.3.13 → 0.3.17
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 +46 -29
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -110,8 +110,8 @@ var EnvSchema = z.object({
|
|
|
110
110
|
// ─────────────────────────────────────────────────────────────
|
|
111
111
|
// Headless Agents Configuration
|
|
112
112
|
// ─────────────────────────────────────────────────────────────
|
|
113
|
-
/** Timeout for agent command execution in seconds (default:
|
|
114
|
-
AGENT_EXECUTION_TIMEOUT: z.coerce.number().default(
|
|
113
|
+
/** Timeout for agent command execution in seconds (default: 2 hours) */
|
|
114
|
+
AGENT_EXECUTION_TIMEOUT: z.coerce.number().default(7200),
|
|
115
115
|
CLAUDE_SESSION_LOCK_WAIT_MS: z.coerce.number().default(1500),
|
|
116
116
|
// ─────────────────────────────────────────────────────────────
|
|
117
117
|
// Agent Visibility Configuration
|
|
@@ -343,8 +343,8 @@ var AGENT_COMMANDS = {
|
|
|
343
343
|
}
|
|
344
344
|
};
|
|
345
345
|
var AGENT_EXECUTION_CONFIG = {
|
|
346
|
-
/** Default execution timeout (seconds) -
|
|
347
|
-
DEFAULT_TIMEOUT_SECONDS:
|
|
346
|
+
/** Default execution timeout (seconds) - 2 hours for complex tasks */
|
|
347
|
+
DEFAULT_TIMEOUT_SECONDS: 7200,
|
|
348
348
|
/** Timeout for waiting on process termination during graceful shutdown (ms) */
|
|
349
349
|
GRACEFUL_SHUTDOWN_TIMEOUT_MS: 2e3,
|
|
350
350
|
/** Maximum buffer size for JSON line parsing (bytes) */
|
|
@@ -2466,7 +2466,7 @@ var Session = class {
|
|
|
2466
2466
|
this._workspacePath = props.workspacePath;
|
|
2467
2467
|
this._workingDir = props.workingDir;
|
|
2468
2468
|
this._status = "active";
|
|
2469
|
-
this._createdAt = /* @__PURE__ */ new Date();
|
|
2469
|
+
this._createdAt = props.createdAt ?? /* @__PURE__ */ new Date();
|
|
2470
2470
|
this._lastActivityAt = /* @__PURE__ */ new Date();
|
|
2471
2471
|
}
|
|
2472
2472
|
get id() {
|
|
@@ -5173,7 +5173,8 @@ var MessageRepository = class {
|
|
|
5173
5173
|
const result = db2.select({ maxSeq: max(messages.sequence) }).from(messages).where(eq3(messages.sessionId, params.sessionId)).get();
|
|
5174
5174
|
const nextSequence = (result?.maxSeq ?? 0) + 1;
|
|
5175
5175
|
const newMessage = {
|
|
5176
|
-
id: nanoid2(16),
|
|
5176
|
+
id: params.messageId ?? nanoid2(16),
|
|
5177
|
+
// Use provided ID or generate new one
|
|
5177
5178
|
sessionId: params.sessionId,
|
|
5178
5179
|
sequence: nextSequence,
|
|
5179
5180
|
role: params.role,
|
|
@@ -5846,8 +5847,9 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5846
5847
|
* Saves a supervisor message to the database.
|
|
5847
5848
|
* Messages are shared across all devices connected to this workstation.
|
|
5848
5849
|
* @param contentBlocks - Optional structured content blocks (for assistant messages)
|
|
5850
|
+
* @param messageId - Optional ID to use (e.g., streaming_message_id for deduplication)
|
|
5849
5851
|
*/
|
|
5850
|
-
saveSupervisorMessage(role, content, contentBlocks) {
|
|
5852
|
+
saveSupervisorMessage(role, content, contentBlocks, messageId) {
|
|
5851
5853
|
this.ensureSupervisorSession();
|
|
5852
5854
|
const sessionId = _ChatHistoryService.SUPERVISOR_SESSION_ID;
|
|
5853
5855
|
const params = {
|
|
@@ -5856,7 +5858,9 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5856
5858
|
contentType: "text",
|
|
5857
5859
|
content,
|
|
5858
5860
|
contentBlocks: contentBlocks ? JSON.stringify(contentBlocks) : void 0,
|
|
5859
|
-
isComplete: true
|
|
5861
|
+
isComplete: true,
|
|
5862
|
+
messageId
|
|
5863
|
+
// Pass through to repository for consistent IDs
|
|
5860
5864
|
};
|
|
5861
5865
|
const saved = this.messageRepo.create(params);
|
|
5862
5866
|
this.logger.debug(
|
|
@@ -5945,14 +5949,16 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5945
5949
|
* @param content - Text content (summary for assistant messages)
|
|
5946
5950
|
* @param contentBlocks - Structured content blocks for rich UI
|
|
5947
5951
|
*/
|
|
5948
|
-
saveAgentMessage(sessionId, role, content, contentBlocks) {
|
|
5952
|
+
saveAgentMessage(sessionId, role, content, contentBlocks, messageId) {
|
|
5949
5953
|
const params = {
|
|
5950
5954
|
sessionId,
|
|
5951
5955
|
role,
|
|
5952
5956
|
contentType: "text",
|
|
5953
5957
|
content,
|
|
5954
5958
|
contentBlocks: contentBlocks ? JSON.stringify(contentBlocks) : void 0,
|
|
5955
|
-
isComplete: true
|
|
5959
|
+
isComplete: true,
|
|
5960
|
+
messageId
|
|
5961
|
+
// Pass through to repository
|
|
5956
5962
|
};
|
|
5957
5963
|
const saved = this.messageRepo.create(params);
|
|
5958
5964
|
this.logger.debug(
|
|
@@ -10608,7 +10614,7 @@ async function bootstrap() {
|
|
|
10608
10614
|
const isExecuting = supervisorAgent.isProcessing();
|
|
10609
10615
|
let currentStreamingBlocks;
|
|
10610
10616
|
let streamingMessageId;
|
|
10611
|
-
if (isExecuting
|
|
10617
|
+
if (isExecuting) {
|
|
10612
10618
|
const blocks = supervisorMessageAccumulator.get();
|
|
10613
10619
|
if (blocks.length > 0) {
|
|
10614
10620
|
currentStreamingBlocks = blocks;
|
|
@@ -10664,7 +10670,7 @@ async function bootstrap() {
|
|
|
10664
10670
|
const isExecuting = agentSessionManager.isExecuting(sessionId);
|
|
10665
10671
|
let currentStreamingBlocks;
|
|
10666
10672
|
let streamingMessageId;
|
|
10667
|
-
if (isExecuting
|
|
10673
|
+
if (isExecuting) {
|
|
10668
10674
|
const blocks = agentMessageAccumulator.get(sessionId);
|
|
10669
10675
|
if (blocks && blocks.length > 0) {
|
|
10670
10676
|
currentStreamingBlocks = blocks;
|
|
@@ -10920,11 +10926,11 @@ async function bootstrap() {
|
|
|
10920
10926
|
);
|
|
10921
10927
|
}
|
|
10922
10928
|
}
|
|
10929
|
+
const accumulatedBlocks = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10930
|
+
const streamingMessageId = getOrCreateAgentStreamingMessageId(sessionId);
|
|
10923
10931
|
let fullTextContent = "";
|
|
10924
10932
|
if (isComplete) {
|
|
10925
|
-
|
|
10926
|
-
agentMessageAccumulator.delete(sessionId);
|
|
10927
|
-
if (allBlocks.length === 0) {
|
|
10933
|
+
if (accumulatedBlocks.length === 0) {
|
|
10928
10934
|
logger.warn(
|
|
10929
10935
|
{
|
|
10930
10936
|
sessionId,
|
|
@@ -10934,8 +10940,8 @@ async function bootstrap() {
|
|
|
10934
10940
|
"Completion received but no blocks were accumulated - blocks may be lost"
|
|
10935
10941
|
);
|
|
10936
10942
|
}
|
|
10937
|
-
if (
|
|
10938
|
-
const blockTypeCounts =
|
|
10943
|
+
if (accumulatedBlocks.length > 0) {
|
|
10944
|
+
const blockTypeCounts = accumulatedBlocks.reduce(
|
|
10939
10945
|
(acc, b) => {
|
|
10940
10946
|
acc[b.block_type] = (acc[b.block_type] ?? 0) + 1;
|
|
10941
10947
|
return acc;
|
|
@@ -10945,26 +10951,25 @@ async function bootstrap() {
|
|
|
10945
10951
|
logger.info(
|
|
10946
10952
|
{
|
|
10947
10953
|
sessionId,
|
|
10948
|
-
totalBlocks:
|
|
10954
|
+
totalBlocks: accumulatedBlocks.length,
|
|
10949
10955
|
blockTypes: blockTypeCounts
|
|
10950
10956
|
},
|
|
10951
10957
|
"Saving agent message with accumulated blocks"
|
|
10952
10958
|
);
|
|
10953
|
-
fullTextContent =
|
|
10954
|
-
const hasError =
|
|
10959
|
+
fullTextContent = accumulatedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
10960
|
+
const hasError = accumulatedBlocks.some((b) => b.block_type === "error");
|
|
10955
10961
|
const role = hasError ? "system" : "assistant";
|
|
10956
10962
|
chatHistoryService.saveAgentMessage(
|
|
10957
10963
|
sessionId,
|
|
10958
10964
|
role,
|
|
10959
10965
|
fullTextContent,
|
|
10960
|
-
|
|
10966
|
+
accumulatedBlocks,
|
|
10967
|
+
streamingMessageId
|
|
10961
10968
|
);
|
|
10962
10969
|
}
|
|
10963
10970
|
}
|
|
10964
|
-
const accumulatedBlocks = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10965
10971
|
const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
|
|
10966
10972
|
const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
10967
|
-
const streamingMessageId = getOrCreateAgentStreamingMessageId(sessionId);
|
|
10968
10973
|
const outputEvent = {
|
|
10969
10974
|
type: "session.output",
|
|
10970
10975
|
session_id: sessionId,
|
|
@@ -10985,6 +10990,7 @@ async function bootstrap() {
|
|
|
10985
10990
|
);
|
|
10986
10991
|
if (isComplete) {
|
|
10987
10992
|
clearAgentStreamingMessageId(sessionId);
|
|
10993
|
+
agentMessageAccumulator.delete(sessionId);
|
|
10988
10994
|
}
|
|
10989
10995
|
if (isComplete && fullTextContent.length > 0) {
|
|
10990
10996
|
const pendingVoiceCommand = pendingAgentVoiceCommands.get(sessionId);
|
|
@@ -11105,7 +11111,8 @@ async function bootstrap() {
|
|
|
11105
11111
|
chatHistoryService.saveSupervisorMessage(
|
|
11106
11112
|
"assistant",
|
|
11107
11113
|
finalOutput,
|
|
11108
|
-
mergedBlocks
|
|
11114
|
+
mergedBlocks,
|
|
11115
|
+
streamingMessageId ?? void 0
|
|
11109
11116
|
);
|
|
11110
11117
|
const pendingVoiceCommand = pendingSupervisorVoiceCommands.get(deviceId);
|
|
11111
11118
|
if (pendingVoiceCommand && ttsService) {
|
|
@@ -11321,7 +11328,7 @@ async function bootstrap() {
|
|
|
11321
11328
|
} catch (error) {
|
|
11322
11329
|
logger.error({ error }, "Failed to connect to tunnel (will retry)");
|
|
11323
11330
|
}
|
|
11324
|
-
const SHUTDOWN_TIMEOUT_MS =
|
|
11331
|
+
const SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
11325
11332
|
let isShuttingDown = false;
|
|
11326
11333
|
const shutdown = async (signal) => {
|
|
11327
11334
|
if (isShuttingDown) {
|
|
@@ -11342,7 +11349,7 @@ async function bootstrap() {
|
|
|
11342
11349
|
logger.info("Terminating all sessions...");
|
|
11343
11350
|
const terminatePromise = sessionManager.terminateAll();
|
|
11344
11351
|
const sessionTimeoutPromise = new Promise((_, reject) => {
|
|
11345
|
-
setTimeout(() => reject(new Error("Session termination timeout")),
|
|
11352
|
+
setTimeout(() => reject(new Error("Session termination timeout")), 2e3);
|
|
11346
11353
|
});
|
|
11347
11354
|
try {
|
|
11348
11355
|
await Promise.race([terminatePromise, sessionTimeoutPromise]);
|
|
@@ -11353,7 +11360,7 @@ async function bootstrap() {
|
|
|
11353
11360
|
logger.info("Closing HTTP server...");
|
|
11354
11361
|
const closePromise = app.close();
|
|
11355
11362
|
const closeTimeoutPromise = new Promise((_, reject) => {
|
|
11356
|
-
setTimeout(() => reject(new Error("HTTP server close timeout")),
|
|
11363
|
+
setTimeout(() => reject(new Error("HTTP server close timeout")), 1e3);
|
|
11357
11364
|
});
|
|
11358
11365
|
try {
|
|
11359
11366
|
await Promise.race([closePromise, closeTimeoutPromise]);
|
|
@@ -11371,8 +11378,18 @@ async function bootstrap() {
|
|
|
11371
11378
|
process.exit(1);
|
|
11372
11379
|
}
|
|
11373
11380
|
};
|
|
11374
|
-
|
|
11375
|
-
|
|
11381
|
+
let signalCount = 0;
|
|
11382
|
+
const handleSignal = (signal) => {
|
|
11383
|
+
signalCount++;
|
|
11384
|
+
if (signalCount === 1) {
|
|
11385
|
+
void shutdown(signal);
|
|
11386
|
+
} else {
|
|
11387
|
+
logger.warn({ signal, count: signalCount }, "Force exit on repeated signal");
|
|
11388
|
+
process.exit(1);
|
|
11389
|
+
}
|
|
11390
|
+
};
|
|
11391
|
+
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
11392
|
+
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
11376
11393
|
process.on("unhandledRejection", (reason, promise) => {
|
|
11377
11394
|
logger.error({ reason, promise }, "Unhandled rejection");
|
|
11378
11395
|
});
|
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.17",
|
|
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",
|