feat(slack): add threaded CEO conversations and live progress

This commit is contained in:
Scott Tong committed 2026-09-23 00:12:02 -07:00
1 parent 91339784ce
commit fbcae3cd88
39 files changed
+2384 -74

No files matched your search

@@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { createAcpxChatProgress } from "./chat-progress.js";
describe("ACPX coarse chat progress", () => {
it("emits only a closed tool signal and coalesces token-by-token updates", () => {
let time = 0;
const project = createAcpxChatProgress(() => time);
for (const type of ["text_delta", "status", "error", "done", "private command"]) expect(project(type)).toBeNull();
expect(project("tool_call")).toEqual({ eventType: "chat.progress.using_tools", stream: "system", level: "info" });
for (let i = 0; i < 100; i++) expect(project("tool_call")).toBeNull();
time = 20_000;
expect(project("tool_call")).not.toBeNull();
expect(createAcpxChatProgress(() => time)("tool_call")).not.toBeNull();
});
});
@@ -0,0 +1,12 @@
import type { AdapterRuntimeEvent } from "../types.js";
/** Only a closed activity label enters the local run-event table. Never accept
* a tool title, command, argument, output, or thought delta as display text. */
export function createAcpxChatProgress(now: () => number = Date.now) {
let lastToolEventAt = -Infinity;
return (eventType: string): AdapterRuntimeEvent | null => {
if (eventType !== "tool_call" || now() - lastToolEventAt < 20_000) return null;
lastToolEventAt = now();
return { eventType: "chat.progress.using_tools", stream: "system", level: "info" };
};
}
@@ -1,4 +1,5 @@
import { cancellableSandboxStartup } from "./startup-cancellation.js";
import { createAcpxChatProgress } from "./chat-progress.js";
import fs from "node:fs/promises";
import fsSync from "node:fs";
import os from "node:os";
@@ -4835,8 +4836,11 @@ export function createAcpxEngineExecutor(deps: AcpxEngineExecutorOptions = {}) {
const stepEventRelay = async (): Promise<AcpRuntimeTurnResult> => {
const turn = activeTurn as AcpRuntimeTurn;
const toolTitles = new Map<string, string>();
const chatProgress = createAcpxChatProgress(now);
const drainEvents = (async (): Promise<void> => {
for await (const event of turn.events) {
const progress = chatProgress(event.type);
if (progress) await ctx.onEvent?.(progress);
// ACPX currently flattens client-side filesystem/terminal receipts
// into status text. They cannot establish complete action outcomes.
if (event.type === "status" && /^(fs|terminal)\//.test(event.text)) incompleteToolInventory = true;