Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@corbits/agent-runtime": "workspace:*",
"@corbits/approvals": "workspace:*",
"@corbits/commands": "workspace:*",
"@corbits/error-sink": "workspace:*",
"@corbits/folded-runs": "workspace:*",
"@corbits/memory": "github:corbitsdev/corbits-memory#9e6f213fa2c002b531d3f6af1aa0abd737b8afe3",
"@corbits/turn-artifacts": "workspace:*",
Expand Down
25 changes: 20 additions & 5 deletions packages/chat/src/workbench-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// full `ChatPlatform`/`ChatStore`.
import { generateId } from "@intx/hub-common";
import { getLogger } from "@intx/log";
import { reportError } from "@corbits/error-sink";
import { InferenceResolutionError } from "@corbits/folded-runs";
import { workbenchTemplate } from "@corbits/workflow-catalog";
import { encodeParts } from "./codec";
Expand Down Expand Up @@ -1216,14 +1217,22 @@ async function dispatchTurnBatch(
requestMessageIds: messageIds,
});
} catch (err) {
const refId = reportError(err, {
operation: "chat.dispatchTurn",
tenantId,
roomId: workbenchId,
agentId: agentAddress,
extra: { messageIds },
});
fanoutLog.error(
"Asking {agentAddress} for a turn failed for workbench " +
"{workbenchId}'s message(s) {messageIds}; posting an " +
"undelivered notice in its voice: {err}",
"{workbenchId}'s message(s) {messageIds} (ref {refId}); " +
"posting an undelivered notice in its voice: {err}",
{
agentAddress,
workbenchId,
messageIds,
refId,
err,
},
);
Expand All @@ -1232,6 +1241,7 @@ async function dispatchTurnBatch(
workbenchId,
agentAddress,
cause: err,
refId,
});
}
}),
Expand Down Expand Up @@ -1351,9 +1361,16 @@ async function postUndeliveredNotice(
readonly workbenchId: string;
readonly agentAddress: string;
readonly cause: unknown;
/** The `reportError` refId for the cause the caller already logged —
* a person can quote this to support instead of the notice reading
* as unexplainable silence. */
readonly refId: string;
},
): Promise<void> {
try {
const notice = isCredentialDispatchFailure(input.cause)
? CREDENTIAL_UNDELIVERED_NOTICE
: RETRYABLE_UNDELIVERED_NOTICE;
await postRoomMessage(deps, {
tenantId: input.tenantId,
workbenchId: input.workbenchId,
Expand All @@ -1362,9 +1379,7 @@ async function postUndeliveredNotice(
parts: [
{
kind: "text",
text: isCredentialDispatchFailure(input.cause)
? CREDENTIAL_UNDELIVERED_NOTICE
: RETRYABLE_UNDELIVERED_NOTICE,
text: `${notice} (ref ${input.refId})`,
turnFailed: true,
},
],
Expand Down
9 changes: 9 additions & 0 deletions packages/chat/test/workbench-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@ describe("message fan-out", () => {
expect(text).toContain("send it again");
expect(text).not.toContain("model key");
});

// CL-6644: a dispatch failure that never surfaces a logged cause is
// unfixable by anyone who cannot read the code — the notice must
// carry a `reportError` refId a person can quote to support, and
// that refId must be the one the caller actually logged.
test("carries a reportError refId a person can quote to support", async () => {
const text = await noticeTextFor(new Error("sidecar unavailable"));
expect(text).toMatch(/\(ref [^)]+\)$/);
});
});

test("a message to a chat delivers to its agent without a mention", async () => {
Expand Down
Loading