@tiflis-io/tiflis-code-workstation 0.3.8 → 0.3.9
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 +252 -53
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -247,18 +247,18 @@ function getProtocolVersion() {
|
|
|
247
247
|
return `${PROTOCOL_VERSION.major}.${PROTOCOL_VERSION.minor}.${PROTOCOL_VERSION.patch}`;
|
|
248
248
|
}
|
|
249
249
|
var CONNECTION_TIMING = {
|
|
250
|
-
/** How often to send ping to tunnel (
|
|
251
|
-
PING_INTERVAL_MS:
|
|
252
|
-
/** Max time to wait for pong before considering connection stale (
|
|
253
|
-
PONG_TIMEOUT_MS:
|
|
254
|
-
/** Max time to wait for registration response (
|
|
255
|
-
REGISTRATION_TIMEOUT_MS:
|
|
256
|
-
/** Minimum reconnect delay (
|
|
257
|
-
RECONNECT_DELAY_MIN_MS:
|
|
258
|
-
/** Maximum reconnect delay (
|
|
259
|
-
RECONNECT_DELAY_MAX_MS:
|
|
260
|
-
/** Interval for checking timed-out client connections (
|
|
261
|
-
CLIENT_TIMEOUT_CHECK_INTERVAL_MS:
|
|
250
|
+
/** How often to send ping to tunnel (5 seconds - fast liveness detection) */
|
|
251
|
+
PING_INTERVAL_MS: 5e3,
|
|
252
|
+
/** Max time to wait for pong before considering connection stale (10 seconds) */
|
|
253
|
+
PONG_TIMEOUT_MS: 1e4,
|
|
254
|
+
/** Max time to wait for registration response (10 seconds) */
|
|
255
|
+
REGISTRATION_TIMEOUT_MS: 1e4,
|
|
256
|
+
/** Minimum reconnect delay (500ms - fast first retry) */
|
|
257
|
+
RECONNECT_DELAY_MIN_MS: 500,
|
|
258
|
+
/** Maximum reconnect delay (5 seconds - don't wait too long) */
|
|
259
|
+
RECONNECT_DELAY_MAX_MS: 5e3,
|
|
260
|
+
/** Interval for checking timed-out client connections (5 seconds - faster cleanup) */
|
|
261
|
+
CLIENT_TIMEOUT_CHECK_INTERVAL_MS: 5e3
|
|
262
262
|
};
|
|
263
263
|
var SESSION_CONFIG = {
|
|
264
264
|
/** Maximum number of concurrent agent sessions */
|
|
@@ -1895,13 +1895,37 @@ import { join as join4 } from "path";
|
|
|
1895
1895
|
import { execSync } from "child_process";
|
|
1896
1896
|
var FileSystemWorkspaceDiscovery = class {
|
|
1897
1897
|
workspacesRoot;
|
|
1898
|
+
cacheTtlMs;
|
|
1899
|
+
/** Cache for workspace list */
|
|
1900
|
+
workspacesCache = null;
|
|
1901
|
+
/** Cache for projects by workspace name */
|
|
1902
|
+
projectsCache = /* @__PURE__ */ new Map();
|
|
1898
1903
|
constructor(config2) {
|
|
1899
1904
|
this.workspacesRoot = config2.workspacesRoot;
|
|
1905
|
+
this.cacheTtlMs = config2.cacheTtlMs ?? 3e4;
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Checks if a cache entry is still valid.
|
|
1909
|
+
*/
|
|
1910
|
+
isCacheValid(entry) {
|
|
1911
|
+
if (!entry) return false;
|
|
1912
|
+
return Date.now() - entry.timestamp < this.cacheTtlMs;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Invalidates all caches. Call when workspace structure changes.
|
|
1916
|
+
*/
|
|
1917
|
+
invalidateCache() {
|
|
1918
|
+
this.workspacesCache = null;
|
|
1919
|
+
this.projectsCache.clear();
|
|
1900
1920
|
}
|
|
1901
1921
|
/**
|
|
1902
1922
|
* Lists all workspaces in the workspaces root.
|
|
1923
|
+
* Results are cached for faster subsequent calls.
|
|
1903
1924
|
*/
|
|
1904
1925
|
async listWorkspaces() {
|
|
1926
|
+
if (this.isCacheValid(this.workspacesCache)) {
|
|
1927
|
+
return this.workspacesCache.data;
|
|
1928
|
+
}
|
|
1905
1929
|
const entries = await readdir(this.workspacesRoot, { withFileTypes: true });
|
|
1906
1930
|
const workspaces = [];
|
|
1907
1931
|
for (const entry of entries) {
|
|
@@ -1915,12 +1939,22 @@ var FileSystemWorkspaceDiscovery = class {
|
|
|
1915
1939
|
});
|
|
1916
1940
|
}
|
|
1917
1941
|
}
|
|
1918
|
-
|
|
1942
|
+
const result = workspaces.sort((a, b) => a.name.localeCompare(b.name));
|
|
1943
|
+
this.workspacesCache = {
|
|
1944
|
+
data: result,
|
|
1945
|
+
timestamp: Date.now()
|
|
1946
|
+
};
|
|
1947
|
+
return result;
|
|
1919
1948
|
}
|
|
1920
1949
|
/**
|
|
1921
1950
|
* Lists all projects in a workspace.
|
|
1951
|
+
* Results are cached for faster subsequent calls.
|
|
1922
1952
|
*/
|
|
1923
1953
|
async listProjects(workspace) {
|
|
1954
|
+
const cached = this.projectsCache.get(workspace);
|
|
1955
|
+
if (this.isCacheValid(cached)) {
|
|
1956
|
+
return cached.data;
|
|
1957
|
+
}
|
|
1924
1958
|
const workspacePath = join4(this.workspacesRoot, workspace);
|
|
1925
1959
|
if (!await this.pathExists(workspacePath)) {
|
|
1926
1960
|
return [];
|
|
@@ -1949,7 +1983,12 @@ var FileSystemWorkspaceDiscovery = class {
|
|
|
1949
1983
|
});
|
|
1950
1984
|
}
|
|
1951
1985
|
}
|
|
1952
|
-
|
|
1986
|
+
const result = projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
1987
|
+
this.projectsCache.set(workspace, {
|
|
1988
|
+
data: result,
|
|
1989
|
+
timestamp: Date.now()
|
|
1990
|
+
});
|
|
1991
|
+
return result;
|
|
1953
1992
|
}
|
|
1954
1993
|
/**
|
|
1955
1994
|
* Gets information about a specific project.
|
|
@@ -3141,6 +3180,12 @@ var HeadlessAgentExecutor = class extends EventEmitter {
|
|
|
3141
3180
|
|
|
3142
3181
|
// src/domain/value-objects/content-block.ts
|
|
3143
3182
|
import { randomUUID } from "crypto";
|
|
3183
|
+
function isTextBlock(block) {
|
|
3184
|
+
return block.block_type === "text";
|
|
3185
|
+
}
|
|
3186
|
+
function isToolBlock(block) {
|
|
3187
|
+
return block.block_type === "tool";
|
|
3188
|
+
}
|
|
3144
3189
|
function createTextBlock(content) {
|
|
3145
3190
|
return {
|
|
3146
3191
|
id: randomUUID(),
|
|
@@ -3219,6 +3264,103 @@ function createVoiceOutputBlock(text2, options) {
|
|
|
3219
3264
|
}
|
|
3220
3265
|
};
|
|
3221
3266
|
}
|
|
3267
|
+
function mergeToolBlocks(blocks) {
|
|
3268
|
+
const toolBlocksByUseId = /* @__PURE__ */ new Map();
|
|
3269
|
+
for (const block of blocks) {
|
|
3270
|
+
if (isToolBlock(block) && block.metadata.tool_use_id) {
|
|
3271
|
+
const toolUseId = block.metadata.tool_use_id;
|
|
3272
|
+
const existing = toolBlocksByUseId.get(toolUseId);
|
|
3273
|
+
if (existing) {
|
|
3274
|
+
const mergedStatus = getMergedToolStatus(
|
|
3275
|
+
existing.metadata.tool_status,
|
|
3276
|
+
block.metadata.tool_status
|
|
3277
|
+
);
|
|
3278
|
+
const mergedBlock = {
|
|
3279
|
+
id: existing.id,
|
|
3280
|
+
// Keep original ID
|
|
3281
|
+
block_type: "tool",
|
|
3282
|
+
content: block.metadata.tool_name || existing.content,
|
|
3283
|
+
metadata: {
|
|
3284
|
+
tool_name: block.metadata.tool_name || existing.metadata.tool_name,
|
|
3285
|
+
tool_use_id: toolUseId,
|
|
3286
|
+
tool_input: block.metadata.tool_input || existing.metadata.tool_input,
|
|
3287
|
+
tool_output: block.metadata.tool_output || existing.metadata.tool_output,
|
|
3288
|
+
tool_status: mergedStatus
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
toolBlocksByUseId.set(toolUseId, mergedBlock);
|
|
3292
|
+
} else {
|
|
3293
|
+
toolBlocksByUseId.set(toolUseId, block);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const seenToolUseIds = /* @__PURE__ */ new Set();
|
|
3298
|
+
const result = [];
|
|
3299
|
+
for (const block of blocks) {
|
|
3300
|
+
if (isToolBlock(block) && block.metadata.tool_use_id) {
|
|
3301
|
+
const toolUseId = block.metadata.tool_use_id;
|
|
3302
|
+
if (!seenToolUseIds.has(toolUseId)) {
|
|
3303
|
+
seenToolUseIds.add(toolUseId);
|
|
3304
|
+
const mergedBlock = toolBlocksByUseId.get(toolUseId);
|
|
3305
|
+
if (mergedBlock) {
|
|
3306
|
+
result.push(mergedBlock);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
} else {
|
|
3310
|
+
result.push(block);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
return result;
|
|
3314
|
+
}
|
|
3315
|
+
function getMergedToolStatus(status1, status2) {
|
|
3316
|
+
if (status1 === "completed" || status2 === "completed") {
|
|
3317
|
+
return "completed";
|
|
3318
|
+
}
|
|
3319
|
+
if (status1 === "failed" || status2 === "failed") {
|
|
3320
|
+
return "failed";
|
|
3321
|
+
}
|
|
3322
|
+
return "running";
|
|
3323
|
+
}
|
|
3324
|
+
function accumulateBlocks(existing, newBlocks) {
|
|
3325
|
+
for (const block of newBlocks) {
|
|
3326
|
+
if (isToolBlock(block) && block.metadata.tool_use_id) {
|
|
3327
|
+
const toolUseId = block.metadata.tool_use_id;
|
|
3328
|
+
const existingIndex = existing.findIndex(
|
|
3329
|
+
(b) => isToolBlock(b) && b.metadata.tool_use_id === toolUseId
|
|
3330
|
+
);
|
|
3331
|
+
if (existingIndex >= 0) {
|
|
3332
|
+
const existingBlock = existing[existingIndex];
|
|
3333
|
+
const mergedStatus = getMergedToolStatus(
|
|
3334
|
+
existingBlock.metadata.tool_status,
|
|
3335
|
+
block.metadata.tool_status
|
|
3336
|
+
);
|
|
3337
|
+
existing[existingIndex] = {
|
|
3338
|
+
id: existingBlock.id,
|
|
3339
|
+
block_type: "tool",
|
|
3340
|
+
content: block.metadata.tool_name || existingBlock.content,
|
|
3341
|
+
metadata: {
|
|
3342
|
+
tool_name: block.metadata.tool_name || existingBlock.metadata.tool_name,
|
|
3343
|
+
tool_use_id: toolUseId,
|
|
3344
|
+
tool_input: block.metadata.tool_input || existingBlock.metadata.tool_input,
|
|
3345
|
+
tool_output: block.metadata.tool_output || existingBlock.metadata.tool_output,
|
|
3346
|
+
tool_status: mergedStatus
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
} else {
|
|
3350
|
+
existing.push(block);
|
|
3351
|
+
}
|
|
3352
|
+
} else if (isTextBlock(block)) {
|
|
3353
|
+
const lastBlock = existing[existing.length - 1];
|
|
3354
|
+
if (lastBlock && isTextBlock(lastBlock)) {
|
|
3355
|
+
existing[existing.length - 1] = block;
|
|
3356
|
+
} else {
|
|
3357
|
+
existing.push(block);
|
|
3358
|
+
}
|
|
3359
|
+
} else {
|
|
3360
|
+
existing.push(block);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3222
3364
|
|
|
3223
3365
|
// src/infrastructure/agents/agent-output-parser.ts
|
|
3224
3366
|
var AgentOutputParser = class _AgentOutputParser {
|
|
@@ -5598,7 +5740,7 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5598
5740
|
* Gets supervisor chat history (global, shared across all devices).
|
|
5599
5741
|
* Returns messages sorted by sequence (oldest first) for chronological display.
|
|
5600
5742
|
*/
|
|
5601
|
-
getSupervisorHistory(limit =
|
|
5743
|
+
getSupervisorHistory(limit = 20) {
|
|
5602
5744
|
const sessionId = _ChatHistoryService.SUPERVISOR_SESSION_ID;
|
|
5603
5745
|
const rows = this.messageRepo.getBySession(sessionId, limit);
|
|
5604
5746
|
return rows.reverse().map((row) => {
|
|
@@ -5663,7 +5805,7 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5663
5805
|
* Gets agent session chat history.
|
|
5664
5806
|
* Returns messages sorted chronologically (oldest first).
|
|
5665
5807
|
*/
|
|
5666
|
-
getAgentHistory(sessionId, limit =
|
|
5808
|
+
getAgentHistory(sessionId, limit = 20) {
|
|
5667
5809
|
const rows = this.messageRepo.getBySession(sessionId, limit);
|
|
5668
5810
|
return rows.reverse().map((row) => {
|
|
5669
5811
|
let contentBlocks;
|
|
@@ -5721,7 +5863,7 @@ var ChatHistoryService = class _ChatHistoryService {
|
|
|
5721
5863
|
* @param sessionIds - List of active agent session IDs
|
|
5722
5864
|
* @param limit - Max messages per session
|
|
5723
5865
|
*/
|
|
5724
|
-
getAllAgentHistories(sessionIds, limit =
|
|
5866
|
+
getAllAgentHistories(sessionIds, limit = 20) {
|
|
5725
5867
|
const histories = /* @__PURE__ */ new Map();
|
|
5726
5868
|
for (const sessionId of sessionIds) {
|
|
5727
5869
|
const history = this.getAgentHistory(sessionId, limit);
|
|
@@ -7346,12 +7488,7 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7346
7488
|
if (this.isExecuting && !this.isCancelled) {
|
|
7347
7489
|
const textBlock = createTextBlock(content);
|
|
7348
7490
|
this.emit("blocks", deviceId, [textBlock], false);
|
|
7349
|
-
|
|
7350
|
-
if (lastTextIndex >= 0) {
|
|
7351
|
-
allBlocks[lastTextIndex] = textBlock;
|
|
7352
|
-
} else {
|
|
7353
|
-
allBlocks.push(textBlock);
|
|
7354
|
-
}
|
|
7491
|
+
accumulateBlocks(allBlocks, [textBlock]);
|
|
7355
7492
|
}
|
|
7356
7493
|
} else if (Array.isArray(content)) {
|
|
7357
7494
|
for (const item of content) {
|
|
@@ -7359,7 +7496,7 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7359
7496
|
const block = this.parseContentItem(item);
|
|
7360
7497
|
if (block) {
|
|
7361
7498
|
this.emit("blocks", deviceId, [block], false);
|
|
7362
|
-
allBlocks
|
|
7499
|
+
accumulateBlocks(allBlocks, [block]);
|
|
7363
7500
|
}
|
|
7364
7501
|
}
|
|
7365
7502
|
}
|
|
@@ -7368,21 +7505,24 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7368
7505
|
if (lastMessage.getType() === "tool" && this.isExecuting && !this.isCancelled) {
|
|
7369
7506
|
const toolContent = lastMessage.content;
|
|
7370
7507
|
const toolName = lastMessage.name ?? "tool";
|
|
7508
|
+
const toolCallId = lastMessage.tool_call_id;
|
|
7371
7509
|
const toolBlock = createToolBlock(
|
|
7372
7510
|
toolName,
|
|
7373
7511
|
"completed",
|
|
7374
7512
|
void 0,
|
|
7375
|
-
typeof toolContent === "string" ? toolContent : JSON.stringify(toolContent)
|
|
7513
|
+
typeof toolContent === "string" ? toolContent : JSON.stringify(toolContent),
|
|
7514
|
+
toolCallId
|
|
7376
7515
|
);
|
|
7377
7516
|
this.emit("blocks", deviceId, [toolBlock], false);
|
|
7378
|
-
allBlocks
|
|
7517
|
+
accumulateBlocks(allBlocks, [toolBlock]);
|
|
7379
7518
|
}
|
|
7380
7519
|
}
|
|
7381
7520
|
if (this.isExecuting && !this.isCancelled) {
|
|
7382
7521
|
this.addToHistory("user", command);
|
|
7383
7522
|
this.addToHistory("assistant", finalOutput);
|
|
7523
|
+
const finalBlocks = mergeToolBlocks(allBlocks);
|
|
7384
7524
|
const completionBlock = createStatusBlock("Complete");
|
|
7385
|
-
this.emit("blocks", deviceId, [completionBlock], true, finalOutput,
|
|
7525
|
+
this.emit("blocks", deviceId, [completionBlock], true, finalOutput, finalBlocks);
|
|
7386
7526
|
this.logger.debug({ output: finalOutput.slice(0, 200) }, "Supervisor streaming completed");
|
|
7387
7527
|
} else {
|
|
7388
7528
|
this.logger.info({ deviceId, isCancelled: this.isCancelled, isExecuting: this.isExecuting }, "Supervisor streaming ended due to cancellation");
|
|
@@ -7412,7 +7552,8 @@ var SupervisorAgent = class extends EventEmitter4 {
|
|
|
7412
7552
|
if (type === "tool_use") {
|
|
7413
7553
|
const name = typeof item.name === "string" ? item.name : "tool";
|
|
7414
7554
|
const input = item.input;
|
|
7415
|
-
|
|
7555
|
+
const toolUseId = typeof item.id === "string" ? item.id : void 0;
|
|
7556
|
+
return createToolBlock(name, "running", input, void 0, toolUseId);
|
|
7416
7557
|
}
|
|
7417
7558
|
return null;
|
|
7418
7559
|
}
|
|
@@ -7677,6 +7818,9 @@ Creating worktrees with \`create_worktree\`:
|
|
|
7677
7818
|
- When creating sessions, confirm the workspace and project first
|
|
7678
7819
|
- For ambiguous requests, ask clarifying questions
|
|
7679
7820
|
- Format responses for terminal display (avoid markdown links)
|
|
7821
|
+
- NEVER use tables - they display poorly on mobile devices
|
|
7822
|
+
- ALWAYS use bullet lists or numbered lists instead of tables
|
|
7823
|
+
- Keep list items short and scannable for mobile reading
|
|
7680
7824
|
- ALWAYS prioritize safety - check before deleting/merging`;
|
|
7681
7825
|
return [new HumanMessage(`[System Instructions]
|
|
7682
7826
|
${systemPrompt}
|
|
@@ -8585,6 +8729,8 @@ var SummarizationService = class {
|
|
|
8585
8729
|
buildSystemPrompt() {
|
|
8586
8730
|
return `You are a concise summarizer for voice output. Summarize AI assistant responses into 1-${this.maxSentences} SHORT sentences.
|
|
8587
8731
|
|
|
8732
|
+
CRITICAL: ALWAYS OUTPUT IN ENGLISH. Translate any non-English content to English.
|
|
8733
|
+
|
|
8588
8734
|
STRICT RULES:
|
|
8589
8735
|
- Maximum 20 words total (hard limit)
|
|
8590
8736
|
- Prefer 1 sentence when possible, 2 only if essential
|
|
@@ -8592,6 +8738,7 @@ STRICT RULES:
|
|
|
8592
8738
|
- Natural spoken language only
|
|
8593
8739
|
- Never use markdown, bullets, or formatting
|
|
8594
8740
|
- Never start with "I" - use passive voice or action verbs
|
|
8741
|
+
- ALWAYS translate to English regardless of input language
|
|
8595
8742
|
|
|
8596
8743
|
ABSOLUTELY FORBIDDEN (never include these in output):
|
|
8597
8744
|
- Session IDs or any alphanumeric identifiers (e.g., "session-abc123", "id: 7f3a2b", UUIDs)
|
|
@@ -8599,6 +8746,7 @@ ABSOLUTELY FORBIDDEN (never include these in output):
|
|
|
8599
8746
|
- Any string that looks like a technical identifier, hash, or token
|
|
8600
8747
|
- Code snippets, variable names, or technical jargon
|
|
8601
8748
|
- Long lists or enumerations
|
|
8749
|
+
- Non-English output (always translate to English)
|
|
8602
8750
|
|
|
8603
8751
|
If the original text contains session IDs or paths, OMIT them entirely. Just describe what happened.
|
|
8604
8752
|
|
|
@@ -8611,7 +8759,9 @@ GOOD examples:
|
|
|
8611
8759
|
BAD examples (never do this):
|
|
8612
8760
|
- "Created session abc-123-def in /Users/roman/work/project" \u274C
|
|
8613
8761
|
- "Session ID is 7f3a2b1c" \u274C
|
|
8614
|
-
- "Working in /home/user/documents/code" \u274C
|
|
8762
|
+
- "Working in /home/user/documents/code" \u274C
|
|
8763
|
+
- "\u0421\u043E\u0437\u0434\u0430\u043D\u0430 \u043D\u043E\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F Claude." \u274C (non-English)
|
|
8764
|
+
- "Sesi\xF3n creada exitosamente." \u274C (non-English)`;
|
|
8615
8765
|
}
|
|
8616
8766
|
/**
|
|
8617
8767
|
* Builds the user prompt with the text to summarize and optional context.
|
|
@@ -9113,22 +9263,29 @@ async function bootstrap() {
|
|
|
9113
9263
|
).map((s) => s.session_id);
|
|
9114
9264
|
const agentHistoriesMap = chatHistoryService.getAllAgentHistories(agentSessionIds);
|
|
9115
9265
|
const agentHistories = {};
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
msg.
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9266
|
+
const historyEntries = Array.from(agentHistoriesMap.entries());
|
|
9267
|
+
const processedHistories = await Promise.all(
|
|
9268
|
+
historyEntries.map(async ([sessionId, history]) => {
|
|
9269
|
+
const enrichedHistory = await Promise.all(
|
|
9270
|
+
history.map(async (msg) => ({
|
|
9271
|
+
sequence: msg.sequence,
|
|
9272
|
+
role: msg.role,
|
|
9273
|
+
content: msg.content,
|
|
9274
|
+
content_blocks: await chatHistoryService.enrichBlocksWithAudio(
|
|
9275
|
+
msg.contentBlocks,
|
|
9276
|
+
msg.audioOutputPath,
|
|
9277
|
+
msg.audioInputPath,
|
|
9278
|
+
false
|
|
9279
|
+
// Don't include audio in sync.state
|
|
9280
|
+
),
|
|
9281
|
+
createdAt: msg.createdAt.toISOString()
|
|
9282
|
+
}))
|
|
9283
|
+
);
|
|
9284
|
+
return { sessionId, history: enrichedHistory };
|
|
9285
|
+
})
|
|
9286
|
+
);
|
|
9287
|
+
for (const { sessionId, history } of processedHistories) {
|
|
9288
|
+
agentHistories[sessionId] = history;
|
|
9132
9289
|
}
|
|
9133
9290
|
const availableAgentsMap = getAvailableAgents();
|
|
9134
9291
|
const availableAgents = Array.from(availableAgentsMap.values()).map(
|
|
@@ -9262,6 +9419,21 @@ async function bootstrap() {
|
|
|
9262
9419
|
}
|
|
9263
9420
|
let commandText;
|
|
9264
9421
|
const messageId = commandMessage.payload.message_id;
|
|
9422
|
+
if (messageBroadcaster) {
|
|
9423
|
+
const ackMessageId = messageId || commandMessage.id;
|
|
9424
|
+
const ackMessage = {
|
|
9425
|
+
type: "message.ack",
|
|
9426
|
+
payload: {
|
|
9427
|
+
message_id: ackMessageId,
|
|
9428
|
+
status: "received"
|
|
9429
|
+
}
|
|
9430
|
+
};
|
|
9431
|
+
messageBroadcaster.sendToClient(deviceId, JSON.stringify(ackMessage));
|
|
9432
|
+
logger.debug(
|
|
9433
|
+
{ messageId: ackMessageId, deviceId },
|
|
9434
|
+
"Sent message.ack for supervisor.command"
|
|
9435
|
+
);
|
|
9436
|
+
}
|
|
9265
9437
|
supervisorAgent.resetCancellationState();
|
|
9266
9438
|
let abortController;
|
|
9267
9439
|
if (commandMessage.payload.audio) {
|
|
@@ -9734,6 +9906,22 @@ async function bootstrap() {
|
|
|
9734
9906
|
const deviceId = execMessage.device_id;
|
|
9735
9907
|
const sessionId = execMessage.session_id;
|
|
9736
9908
|
const messageId = execMessage.payload.message_id;
|
|
9909
|
+
if (deviceId && messageBroadcaster) {
|
|
9910
|
+
const ackMessageId = messageId || execMessage.id;
|
|
9911
|
+
const ackMessage = {
|
|
9912
|
+
type: "message.ack",
|
|
9913
|
+
payload: {
|
|
9914
|
+
message_id: ackMessageId,
|
|
9915
|
+
session_id: sessionId,
|
|
9916
|
+
status: "received"
|
|
9917
|
+
}
|
|
9918
|
+
};
|
|
9919
|
+
messageBroadcaster.sendToClient(deviceId, JSON.stringify(ackMessage));
|
|
9920
|
+
logger.debug(
|
|
9921
|
+
{ messageId: ackMessageId, sessionId, deviceId },
|
|
9922
|
+
"Sent message.ack for session.execute"
|
|
9923
|
+
);
|
|
9924
|
+
}
|
|
9737
9925
|
cancelledDuringTranscription.delete(sessionId);
|
|
9738
9926
|
if (execMessage.payload.audio) {
|
|
9739
9927
|
logger.info(
|
|
@@ -10496,7 +10684,7 @@ async function bootstrap() {
|
|
|
10496
10684
|
if (persistableBlocks.length > 0) {
|
|
10497
10685
|
const accumulated = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10498
10686
|
const wasEmpty = accumulated.length === 0;
|
|
10499
|
-
accumulated
|
|
10687
|
+
accumulateBlocks(accumulated, persistableBlocks);
|
|
10500
10688
|
agentMessageAccumulator.set(sessionId, accumulated);
|
|
10501
10689
|
if (wasEmpty) {
|
|
10502
10690
|
logger.debug(
|
|
@@ -10551,7 +10739,8 @@ async function bootstrap() {
|
|
|
10551
10739
|
}
|
|
10552
10740
|
}
|
|
10553
10741
|
const accumulatedBlocks = agentMessageAccumulator.get(sessionId) ?? [];
|
|
10554
|
-
const
|
|
10742
|
+
const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
|
|
10743
|
+
const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
10555
10744
|
const outputEvent = {
|
|
10556
10745
|
type: "session.output",
|
|
10557
10746
|
session_id: sessionId,
|
|
@@ -10559,8 +10748,8 @@ async function bootstrap() {
|
|
|
10559
10748
|
content_type: "agent",
|
|
10560
10749
|
content: fullAccumulatedText,
|
|
10561
10750
|
// Full accumulated text for backward compat
|
|
10562
|
-
content_blocks:
|
|
10563
|
-
// Full accumulated blocks for rich UI
|
|
10751
|
+
content_blocks: mergedBlocks,
|
|
10752
|
+
// Full accumulated blocks for rich UI (merged)
|
|
10564
10753
|
timestamp: Date.now(),
|
|
10565
10754
|
is_complete: isComplete
|
|
10566
10755
|
}
|
|
@@ -10644,10 +10833,11 @@ async function bootstrap() {
|
|
|
10644
10833
|
}
|
|
10645
10834
|
}
|
|
10646
10835
|
);
|
|
10836
|
+
let supervisorBlockAccumulator = [];
|
|
10647
10837
|
supervisorAgent.on(
|
|
10648
10838
|
"blocks",
|
|
10649
10839
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
10650
|
-
async (deviceId, blocks, isComplete, finalOutput,
|
|
10840
|
+
async (deviceId, blocks, isComplete, finalOutput, _allBlocks) => {
|
|
10651
10841
|
const wasCancelled = supervisorAgent.wasCancelled();
|
|
10652
10842
|
logger.debug(
|
|
10653
10843
|
{ deviceId, blockCount: blocks.length, isComplete, wasCancelled },
|
|
@@ -10658,26 +10848,35 @@ async function bootstrap() {
|
|
|
10658
10848
|
{ deviceId, blockCount: blocks.length, isComplete },
|
|
10659
10849
|
"Ignoring supervisor blocks - execution was cancelled"
|
|
10660
10850
|
);
|
|
10851
|
+
supervisorBlockAccumulator = [];
|
|
10661
10852
|
return;
|
|
10662
10853
|
}
|
|
10663
|
-
const
|
|
10854
|
+
const persistableBlocks = blocks.filter((b) => b.block_type !== "status");
|
|
10855
|
+
if (persistableBlocks.length > 0) {
|
|
10856
|
+
accumulateBlocks(supervisorBlockAccumulator, persistableBlocks);
|
|
10857
|
+
}
|
|
10858
|
+
const mergedBlocks = mergeToolBlocks(supervisorBlockAccumulator);
|
|
10859
|
+
const textContent = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
|
|
10664
10860
|
const outputEvent = {
|
|
10665
10861
|
type: "supervisor.output",
|
|
10666
10862
|
payload: {
|
|
10667
10863
|
content_type: "supervisor",
|
|
10668
10864
|
content: textContent,
|
|
10669
|
-
content_blocks:
|
|
10865
|
+
content_blocks: mergedBlocks,
|
|
10670
10866
|
timestamp: Date.now(),
|
|
10671
10867
|
is_complete: isComplete
|
|
10672
10868
|
}
|
|
10673
10869
|
};
|
|
10674
10870
|
const message = JSON.stringify(outputEvent);
|
|
10675
10871
|
broadcaster.broadcastToAll(message);
|
|
10872
|
+
if (isComplete) {
|
|
10873
|
+
supervisorBlockAccumulator = [];
|
|
10874
|
+
}
|
|
10676
10875
|
if (isComplete && finalOutput && finalOutput.length > 0) {
|
|
10677
10876
|
chatHistoryService.saveSupervisorMessage(
|
|
10678
10877
|
"assistant",
|
|
10679
10878
|
finalOutput,
|
|
10680
|
-
|
|
10879
|
+
mergedBlocks
|
|
10681
10880
|
);
|
|
10682
10881
|
const pendingVoiceCommand = pendingSupervisorVoiceCommands.get(deviceId);
|
|
10683
10882
|
if (pendingVoiceCommand && ttsService) {
|
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.9",
|
|
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",
|