@tiflis-io/tiflis-code-workstation 0.3.27 → 0.3.29
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 +116 -52
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1610,9 +1610,7 @@ var TunnelClient = class {
|
|
|
1610
1610
|
this.handleRegistrationTimeout();
|
|
1611
1611
|
}
|
|
1612
1612
|
}, CONNECTION_TIMING.REGISTRATION_TIMEOUT_MS);
|
|
1613
|
-
|
|
1614
|
-
this.registrationTimeout.unref();
|
|
1615
|
-
}
|
|
1613
|
+
this.registrationTimeout.unref();
|
|
1616
1614
|
}
|
|
1617
1615
|
/**
|
|
1618
1616
|
* Handles registration timeout.
|
|
@@ -1736,9 +1734,7 @@ var TunnelClient = class {
|
|
|
1736
1734
|
this.sendToTunnel(ping);
|
|
1737
1735
|
}
|
|
1738
1736
|
}, CONNECTION_TIMING.PING_INTERVAL_MS);
|
|
1739
|
-
|
|
1740
|
-
this.pingInterval.unref();
|
|
1741
|
-
}
|
|
1737
|
+
this.pingInterval.unref();
|
|
1742
1738
|
}
|
|
1743
1739
|
/**
|
|
1744
1740
|
* Schedules a reconnection attempt.
|
|
@@ -1759,9 +1755,7 @@ var TunnelClient = class {
|
|
|
1759
1755
|
this.scheduleReconnect();
|
|
1760
1756
|
});
|
|
1761
1757
|
}, delay);
|
|
1762
|
-
|
|
1763
|
-
this.reconnectTimeout.unref();
|
|
1764
|
-
}
|
|
1758
|
+
this.reconnectTimeout.unref();
|
|
1765
1759
|
}
|
|
1766
1760
|
/**
|
|
1767
1761
|
* Flushes buffered messages after reconnection.
|
|
@@ -2965,9 +2959,7 @@ var TerminalOutputBatcher = class {
|
|
|
2965
2959
|
if (this.timeout === null) {
|
|
2966
2960
|
const adaptiveInterval = this.outputRate > 1e3 ? this.batchIntervalMs : Math.min(8, this.batchIntervalMs);
|
|
2967
2961
|
this.timeout = setTimeout(() => this.flush(), adaptiveInterval);
|
|
2968
|
-
|
|
2969
|
-
this.timeout.unref();
|
|
2970
|
-
}
|
|
2962
|
+
this.timeout.unref();
|
|
2971
2963
|
}
|
|
2972
2964
|
}
|
|
2973
2965
|
/**
|
|
@@ -3075,7 +3067,7 @@ var HeadlessAgentExecutor = class extends EventEmitter {
|
|
|
3075
3067
|
detached: true,
|
|
3076
3068
|
// Create new process group for clean termination
|
|
3077
3069
|
// Ensure child doesn't interfere with parent's signal handling
|
|
3078
|
-
// @ts-
|
|
3070
|
+
// @ts-expect-error - Node.js 16+ option
|
|
3079
3071
|
ignoreParentSignals: true
|
|
3080
3072
|
});
|
|
3081
3073
|
this.subprocess.stdout?.on("data", (data) => {
|
|
@@ -5121,9 +5113,10 @@ var MessageBroadcasterImpl = class {
|
|
|
5121
5113
|
}
|
|
5122
5114
|
/**
|
|
5123
5115
|
* Broadcasts a message to all clients subscribed to a session (by session ID string).
|
|
5124
|
-
* Sends targeted messages to each subscriber
|
|
5116
|
+
* Sends targeted messages to each subscriber in parallel with timeout.
|
|
5117
|
+
* Prevents slow clients from blocking others.
|
|
5125
5118
|
*/
|
|
5126
|
-
broadcastToSubscribers(sessionId, message) {
|
|
5119
|
+
async broadcastToSubscribers(sessionId, message) {
|
|
5127
5120
|
const session = new SessionId(sessionId);
|
|
5128
5121
|
const subscribers = this.deps.clientRegistry.getSubscribers(session);
|
|
5129
5122
|
const authenticatedSubscribers = subscribers.filter(
|
|
@@ -5132,9 +5125,44 @@ var MessageBroadcasterImpl = class {
|
|
|
5132
5125
|
if (authenticatedSubscribers.length === 0) {
|
|
5133
5126
|
return;
|
|
5134
5127
|
}
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5128
|
+
const SEND_TIMEOUT_MS = 2e3;
|
|
5129
|
+
const sendPromises = authenticatedSubscribers.map(async (client) => {
|
|
5130
|
+
try {
|
|
5131
|
+
await Promise.race([
|
|
5132
|
+
new Promise((resolve2, reject) => {
|
|
5133
|
+
const sent = this.deps.tunnelClient.sendToDevice(
|
|
5134
|
+
client.deviceId.value,
|
|
5135
|
+
message
|
|
5136
|
+
);
|
|
5137
|
+
if (sent) {
|
|
5138
|
+
resolve2();
|
|
5139
|
+
} else {
|
|
5140
|
+
reject(new Error(`sendToDevice returned false for ${client.deviceId.value}`));
|
|
5141
|
+
}
|
|
5142
|
+
}),
|
|
5143
|
+
new Promise(
|
|
5144
|
+
(_, reject) => setTimeout(
|
|
5145
|
+
() => reject(new Error(`Send timeout for ${client.deviceId.value}`)),
|
|
5146
|
+
SEND_TIMEOUT_MS
|
|
5147
|
+
)
|
|
5148
|
+
)
|
|
5149
|
+
]);
|
|
5150
|
+
this.logger.debug(
|
|
5151
|
+
{ deviceId: client.deviceId.value, sessionId },
|
|
5152
|
+
"Message sent to subscriber"
|
|
5153
|
+
);
|
|
5154
|
+
} catch (error) {
|
|
5155
|
+
this.logger.warn(
|
|
5156
|
+
{
|
|
5157
|
+
deviceId: client.deviceId.value,
|
|
5158
|
+
sessionId,
|
|
5159
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5160
|
+
},
|
|
5161
|
+
"Failed to send to subscriber (timeout or error)"
|
|
5162
|
+
);
|
|
5163
|
+
}
|
|
5164
|
+
});
|
|
5165
|
+
await Promise.allSettled(sendPromises);
|
|
5138
5166
|
}
|
|
5139
5167
|
};
|
|
5140
5168
|
|
|
@@ -9237,7 +9265,7 @@ async function bootstrap() {
|
|
|
9237
9265
|
}
|
|
9238
9266
|
shutdownInProgress = true;
|
|
9239
9267
|
logger.info({ signal, count: signalCount }, "Signal received");
|
|
9240
|
-
|
|
9268
|
+
logger.info("Shutting down...");
|
|
9241
9269
|
shutdown2(signal).catch((error) => {
|
|
9242
9270
|
logger.error({ error }, "Shutdown error");
|
|
9243
9271
|
}).finally(() => {
|
|
@@ -10140,7 +10168,7 @@ async function bootstrap() {
|
|
|
10140
10168
|
message_id: messageId
|
|
10141
10169
|
}
|
|
10142
10170
|
};
|
|
10143
|
-
messageBroadcaster.broadcastToSubscribers(
|
|
10171
|
+
await messageBroadcaster.broadcastToSubscribers(
|
|
10144
10172
|
sessionId,
|
|
10145
10173
|
JSON.stringify(errorEvent)
|
|
10146
10174
|
);
|
|
@@ -10176,7 +10204,7 @@ async function bootstrap() {
|
|
|
10176
10204
|
from_device_id: deviceId
|
|
10177
10205
|
}
|
|
10178
10206
|
};
|
|
10179
|
-
messageBroadcaster.broadcastToSubscribers(
|
|
10207
|
+
await messageBroadcaster.broadcastToSubscribers(
|
|
10180
10208
|
sessionId,
|
|
10181
10209
|
JSON.stringify(transcriptionEvent)
|
|
10182
10210
|
);
|
|
@@ -10267,7 +10295,7 @@ async function bootstrap() {
|
|
|
10267
10295
|
timestamp: Date.now()
|
|
10268
10296
|
}
|
|
10269
10297
|
};
|
|
10270
|
-
messageBroadcaster.broadcastToSubscribers(
|
|
10298
|
+
await messageBroadcaster.broadcastToSubscribers(
|
|
10271
10299
|
sessionId,
|
|
10272
10300
|
JSON.stringify(errorEvent)
|
|
10273
10301
|
);
|
|
@@ -10395,14 +10423,20 @@ async function bootstrap() {
|
|
|
10395
10423
|
is_complete: true
|
|
10396
10424
|
}
|
|
10397
10425
|
};
|
|
10398
|
-
messageBroadcaster.broadcastToSubscribers(
|
|
10426
|
+
void messageBroadcaster.broadcastToSubscribers(
|
|
10399
10427
|
sessionId,
|
|
10400
10428
|
JSON.stringify(cancelOutput)
|
|
10401
|
-
)
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10429
|
+
).then(() => {
|
|
10430
|
+
logger.info(
|
|
10431
|
+
{ sessionId },
|
|
10432
|
+
"Broadcasted cancel message to subscribers"
|
|
10433
|
+
);
|
|
10434
|
+
}).catch((error) => {
|
|
10435
|
+
logger.error(
|
|
10436
|
+
{ sessionId, error },
|
|
10437
|
+
"Failed to broadcast cancel message"
|
|
10438
|
+
);
|
|
10439
|
+
});
|
|
10406
10440
|
chatHistoryService.saveAgentMessage(sessionId, "assistant", "", [
|
|
10407
10441
|
cancelBlock
|
|
10408
10442
|
]);
|
|
@@ -10888,9 +10922,13 @@ async function bootstrap() {
|
|
|
10888
10922
|
subscriptionRepository,
|
|
10889
10923
|
logger
|
|
10890
10924
|
});
|
|
10925
|
+
let supervisorMessageSequence = 0;
|
|
10926
|
+
const agentMessageSequences = /* @__PURE__ */ new Map();
|
|
10891
10927
|
const broadcaster = messageBroadcaster;
|
|
10892
10928
|
const agentMessageAccumulator = /* @__PURE__ */ new Map();
|
|
10893
10929
|
const agentStreamingMessageIds = /* @__PURE__ */ new Map();
|
|
10930
|
+
const STREAMING_STATE_GRACE_PERIOD_MS = 1e4;
|
|
10931
|
+
const agentCleanupTimeouts = /* @__PURE__ */ new Map();
|
|
10894
10932
|
const getOrCreateAgentStreamingMessageId = (sessionId) => {
|
|
10895
10933
|
let messageId = agentStreamingMessageIds.get(sessionId);
|
|
10896
10934
|
if (!messageId) {
|
|
@@ -10900,7 +10938,16 @@ async function bootstrap() {
|
|
|
10900
10938
|
return messageId;
|
|
10901
10939
|
};
|
|
10902
10940
|
const clearAgentStreamingMessageId = (sessionId) => {
|
|
10903
|
-
|
|
10941
|
+
const existingTimeout = agentCleanupTimeouts.get(sessionId);
|
|
10942
|
+
if (existingTimeout) {
|
|
10943
|
+
clearTimeout(existingTimeout);
|
|
10944
|
+
}
|
|
10945
|
+
const timeout = setTimeout(() => {
|
|
10946
|
+
agentStreamingMessageIds.delete(sessionId);
|
|
10947
|
+
agentCleanupTimeouts.delete(sessionId);
|
|
10948
|
+
logger.debug({ sessionId }, "Agent streaming state cleaned up after grace period");
|
|
10949
|
+
}, STREAMING_STATE_GRACE_PERIOD_MS);
|
|
10950
|
+
agentCleanupTimeouts.set(sessionId, timeout);
|
|
10904
10951
|
};
|
|
10905
10952
|
agentSessionManager.on(
|
|
10906
10953
|
"blocks",
|
|
@@ -10974,10 +11021,14 @@ async function bootstrap() {
|
|
|
10974
11021
|
}
|
|
10975
11022
|
const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
|
|
10976
11023
|
const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
11024
|
+
const currentSequence = agentMessageSequences.get(sessionId) ?? 0;
|
|
11025
|
+
const newSequence = currentSequence + 1;
|
|
11026
|
+
agentMessageSequences.set(sessionId, newSequence);
|
|
10977
11027
|
const outputEvent = {
|
|
10978
11028
|
type: "session.output",
|
|
10979
11029
|
session_id: sessionId,
|
|
10980
11030
|
streaming_message_id: streamingMessageId,
|
|
11031
|
+
sequence: newSequence,
|
|
10981
11032
|
payload: {
|
|
10982
11033
|
content_type: "agent",
|
|
10983
11034
|
content: fullAccumulatedText,
|
|
@@ -10988,13 +11039,19 @@ async function bootstrap() {
|
|
|
10988
11039
|
is_complete: isComplete
|
|
10989
11040
|
}
|
|
10990
11041
|
};
|
|
10991
|
-
broadcaster.broadcastToSubscribers(
|
|
11042
|
+
await broadcaster.broadcastToSubscribers(
|
|
10992
11043
|
sessionId,
|
|
10993
11044
|
JSON.stringify(outputEvent)
|
|
10994
11045
|
);
|
|
10995
11046
|
if (isComplete) {
|
|
10996
11047
|
clearAgentStreamingMessageId(sessionId);
|
|
10997
|
-
|
|
11048
|
+
setTimeout(() => {
|
|
11049
|
+
agentMessageAccumulator.delete(sessionId);
|
|
11050
|
+
logger.debug(
|
|
11051
|
+
{ sessionId, messageId: streamingMessageId },
|
|
11052
|
+
"Agent message accumulator cleaned up after grace period"
|
|
11053
|
+
);
|
|
11054
|
+
}, STREAMING_STATE_GRACE_PERIOD_MS);
|
|
10998
11055
|
}
|
|
10999
11056
|
if (isComplete && fullTextContent.length > 0) {
|
|
11000
11057
|
const pendingVoiceCommand = pendingAgentVoiceCommands.get(sessionId);
|
|
@@ -11053,7 +11110,7 @@ async function bootstrap() {
|
|
|
11053
11110
|
from_device_id: originDeviceId
|
|
11054
11111
|
}
|
|
11055
11112
|
};
|
|
11056
|
-
broadcaster.broadcastToSubscribers(
|
|
11113
|
+
await broadcaster.broadcastToSubscribers(
|
|
11057
11114
|
sessionId,
|
|
11058
11115
|
JSON.stringify(voiceOutputEvent)
|
|
11059
11116
|
);
|
|
@@ -11098,6 +11155,7 @@ async function bootstrap() {
|
|
|
11098
11155
|
const outputEvent = {
|
|
11099
11156
|
type: "supervisor.output",
|
|
11100
11157
|
streaming_message_id: streamingMessageId,
|
|
11158
|
+
sequence: ++supervisorMessageSequence,
|
|
11101
11159
|
payload: {
|
|
11102
11160
|
content_type: "supervisor",
|
|
11103
11161
|
content: textContent,
|
|
@@ -11109,7 +11167,13 @@ async function bootstrap() {
|
|
|
11109
11167
|
const message = JSON.stringify(outputEvent);
|
|
11110
11168
|
broadcaster.broadcastToAll(message);
|
|
11111
11169
|
if (isComplete) {
|
|
11112
|
-
|
|
11170
|
+
setTimeout(() => {
|
|
11171
|
+
supervisorMessageAccumulator.clear();
|
|
11172
|
+
logger.debug(
|
|
11173
|
+
{ messageId: streamingMessageId },
|
|
11174
|
+
"Supervisor message accumulator cleaned up after grace period"
|
|
11175
|
+
);
|
|
11176
|
+
}, STREAMING_STATE_GRACE_PERIOD_MS);
|
|
11113
11177
|
}
|
|
11114
11178
|
if (isComplete && finalOutput && finalOutput.length > 0) {
|
|
11115
11179
|
chatHistoryService.saveSupervisorMessage(
|
|
@@ -11238,21 +11302,23 @@ async function bootstrap() {
|
|
|
11238
11302
|
batchIntervalMs: env.TERMINAL_BATCH_INTERVAL_MS,
|
|
11239
11303
|
maxBatchSize: env.TERMINAL_BATCH_MAX_SIZE,
|
|
11240
11304
|
onFlush: (batchedData) => {
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11305
|
+
void (async () => {
|
|
11306
|
+
const outputMessage = session.addOutputToBuffer(batchedData);
|
|
11307
|
+
const outputEvent = {
|
|
11308
|
+
type: "session.output",
|
|
11309
|
+
session_id: sessionId.value,
|
|
11310
|
+
payload: {
|
|
11311
|
+
content_type: "terminal",
|
|
11312
|
+
content: batchedData,
|
|
11313
|
+
timestamp: outputMessage.timestamp,
|
|
11314
|
+
sequence: outputMessage.sequence
|
|
11315
|
+
}
|
|
11316
|
+
};
|
|
11317
|
+
await broadcaster.broadcastToSubscribers(
|
|
11318
|
+
sessionId.value,
|
|
11319
|
+
JSON.stringify(outputEvent)
|
|
11320
|
+
);
|
|
11321
|
+
})();
|
|
11256
11322
|
}
|
|
11257
11323
|
});
|
|
11258
11324
|
session.onOutput((data) => {
|
|
@@ -11388,10 +11454,8 @@ async function bootstrap() {
|
|
|
11388
11454
|
};
|
|
11389
11455
|
registerSignalHandlers(shutdown);
|
|
11390
11456
|
}
|
|
11391
|
-
if (process.stdin
|
|
11392
|
-
|
|
11393
|
-
process.stdin.resume();
|
|
11394
|
-
}
|
|
11457
|
+
if (process.stdin.isPaused()) {
|
|
11458
|
+
process.stdin.resume();
|
|
11395
11459
|
}
|
|
11396
11460
|
bootstrap().catch((error) => {
|
|
11397
11461
|
console.error("Failed to bootstrap:", error);
|
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.29",
|
|
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",
|