feat(sessions): add agent-to-agent post step
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
- Agent: add optional per-session Docker sandbox for tool execution (`agent.sandbox`) with allow/deny policy and auto-pruning.
|
- Agent: add optional per-session Docker sandbox for tool execution (`agent.sandbox`) with allow/deny policy and auto-pruning.
|
||||||
- Agent: add sandboxed Chromium browser (CDP + optional noVNC observer) for sandboxed sessions.
|
- Agent: add sandboxed Chromium browser (CDP + optional noVNC observer) for sandboxed sessions.
|
||||||
- Nodes: add `location.get` with Always/Precise settings on macOS/iOS/Android plus CLI/tool support.
|
- Nodes: add `location.get` with Always/Precise settings on macOS/iOS/Android plus CLI/tool support.
|
||||||
|
- Sessions: add agent‑to‑agent post step with `ANNOUNCE_SKIP` to suppress channel announcements.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- CI: fix lint ordering after merge cleanup (#156) — thanks @steipete.
|
- CI: fix lint ordering after merge cleanup (#156) — thanks @steipete.
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
- Queue: clarify steer-backlog behavior with inline commands and update examples for streaming surfaces.
|
- Queue: clarify steer-backlog behavior with inline commands and update examples for streaming surfaces.
|
||||||
- Sandbox: document per-session agent sandbox setup, browser image, and Docker build.
|
- Sandbox: document per-session agent sandbox setup, browser image, and Docker build.
|
||||||
- macOS: clarify menu bar uses sessionKey from agent events.
|
- macOS: clarify menu bar uses sessionKey from agent events.
|
||||||
|
- Sessions: document agent-to-agent post step and `ANNOUNCE_SKIP`.
|
||||||
|
|
||||||
## 2.0.0-beta5 — 2026-01-03
|
## 2.0.0-beta5 — 2026-01-03
|
||||||
|
|
||||||
|
|||||||
@@ -378,6 +378,8 @@ public struct AgentParams: Codable {
|
|||||||
public let deliver: Bool?
|
public let deliver: Bool?
|
||||||
public let channel: String?
|
public let channel: String?
|
||||||
public let timeout: Int?
|
public let timeout: Int?
|
||||||
|
public let lane: String?
|
||||||
|
public let extrasystemprompt: String?
|
||||||
public let idempotencykey: String
|
public let idempotencykey: String
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -389,6 +391,8 @@ public struct AgentParams: Codable {
|
|||||||
deliver: Bool?,
|
deliver: Bool?,
|
||||||
channel: String?,
|
channel: String?,
|
||||||
timeout: Int?,
|
timeout: Int?,
|
||||||
|
lane: String?,
|
||||||
|
extrasystemprompt: String?,
|
||||||
idempotencykey: String
|
idempotencykey: String
|
||||||
) {
|
) {
|
||||||
self.message = message
|
self.message = message
|
||||||
@@ -399,6 +403,8 @@ public struct AgentParams: Codable {
|
|||||||
self.deliver = deliver
|
self.deliver = deliver
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
self.lane = lane
|
||||||
|
self.extrasystemprompt = extrasystemprompt
|
||||||
self.idempotencykey = idempotencykey
|
self.idempotencykey = idempotencykey
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
@@ -410,6 +416,8 @@ public struct AgentParams: Codable {
|
|||||||
case deliver
|
case deliver
|
||||||
case channel
|
case channel
|
||||||
case timeout
|
case timeout
|
||||||
|
case lane
|
||||||
|
case extrasystemprompt = "extraSystemPrompt"
|
||||||
case idempotencykey = "idempotencyKey"
|
case idempotencykey = "idempotencyKey"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ Behavior:
|
|||||||
- If wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
|
- If wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
|
||||||
- If the run fails: `{ runId, status: "error", error }`.
|
- If the run fails: `{ runId, status: "error", error }`.
|
||||||
- Waits via gateway `agent.wait` (server-side) so reconnects don't drop the wait.
|
- Waits via gateway `agent.wait` (server-side) so reconnects don't drop the wait.
|
||||||
|
- Agent-to-agent message context is injected for the primary run.
|
||||||
|
- After the primary run completes, Clawdis starts an **agent-to-agent post step**:
|
||||||
|
- The agent can reply with the announcement to post to the target session.
|
||||||
|
- To stay silent, reply exactly `ANNOUNCE_SKIP`.
|
||||||
|
- Any other reply is sent to the target channel.
|
||||||
|
- The post step includes the original request and round‑1 reply in context.
|
||||||
|
|
||||||
## Provider Field
|
## Provider Field
|
||||||
- For groups, `provider` is the `surface` recorded on the session entry.
|
- For groups, `provider` is the `surface` recorded on the session entry.
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ Notes:
|
|||||||
- `main` is the canonical direct-chat key; global/unknown are hidden.
|
- `main` is the canonical direct-chat key; global/unknown are hidden.
|
||||||
- `messageLimit > 0` fetches last N messages per session (tool messages filtered).
|
- `messageLimit > 0` fetches last N messages per session (tool messages filtered).
|
||||||
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
|
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
|
||||||
|
- `sessions_send` always runs a follow‑up **agent‑to‑agent post step**; reply `ANNOUNCE_SKIP` to suppress the announcement.
|
||||||
|
|
||||||
### `discord`
|
### `discord`
|
||||||
Send Discord reactions, stickers, or polls.
|
Send Discord reactions, stickers, or polls.
|
||||||
|
|||||||
@@ -124,16 +124,38 @@ describe("sessions tools", () => {
|
|||||||
it("sessions_send supports fire-and-forget and wait", async () => {
|
it("sessions_send supports fire-and-forget and wait", async () => {
|
||||||
callGatewayMock.mockReset();
|
callGatewayMock.mockReset();
|
||||||
const calls: Array<{ method?: string; params?: unknown }> = [];
|
const calls: Array<{ method?: string; params?: unknown }> = [];
|
||||||
|
let agentCallCount = 0;
|
||||||
|
let historyCallCount = 0;
|
||||||
|
let sendCallCount = 0;
|
||||||
|
let waitRunId: string | undefined;
|
||||||
|
let nextHistoryIsWaitReply = false;
|
||||||
callGatewayMock.mockImplementation(async (opts: unknown) => {
|
callGatewayMock.mockImplementation(async (opts: unknown) => {
|
||||||
const request = opts as { method?: string; params?: unknown };
|
const request = opts as { method?: string; params?: unknown };
|
||||||
calls.push(request);
|
calls.push(request);
|
||||||
if (request.method === "agent") {
|
if (request.method === "agent") {
|
||||||
return { runId: "run-1", status: "accepted", acceptedAt: 1234 };
|
agentCallCount += 1;
|
||||||
|
const runId = `run-${agentCallCount}`;
|
||||||
|
const params = request.params as { message?: string } | undefined;
|
||||||
|
if (params?.message === "wait") {
|
||||||
|
waitRunId = runId;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runId,
|
||||||
|
status: "accepted",
|
||||||
|
acceptedAt: 1234 + agentCallCount,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (request.method === "agent.wait") {
|
if (request.method === "agent.wait") {
|
||||||
return { runId: "run-1", status: "ok" };
|
const params = request.params as { runId?: string } | undefined;
|
||||||
|
if (params?.runId && params.runId === waitRunId) {
|
||||||
|
nextHistoryIsWaitReply = true;
|
||||||
|
}
|
||||||
|
return { runId: params?.runId ?? "run-1", status: "ok" };
|
||||||
}
|
}
|
||||||
if (request.method === "chat.history") {
|
if (request.method === "chat.history") {
|
||||||
|
historyCallCount += 1;
|
||||||
|
const text = nextHistoryIsWaitReply ? "done" : "ANNOUNCE_SKIP";
|
||||||
|
nextHistoryIsWaitReply = false;
|
||||||
return {
|
return {
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -141,7 +163,7 @@ describe("sessions tools", () => {
|
|||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "done",
|
text,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
timestamp: 20,
|
timestamp: 20,
|
||||||
@@ -149,6 +171,10 @@ describe("sessions tools", () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (request.method === "send") {
|
||||||
|
sendCallCount += 1;
|
||||||
|
return { messageId: "m1" };
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,6 +190,7 @@ describe("sessions tools", () => {
|
|||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
});
|
});
|
||||||
expect(fire.details).toMatchObject({ status: "accepted", runId: "run-1" });
|
expect(fire.details).toMatchObject({ status: "accepted", runId: "run-1" });
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|
||||||
const waitPromise = tool.execute("call6", {
|
const waitPromise = tool.execute("call6", {
|
||||||
sessionKey: "main",
|
sessionKey: "main",
|
||||||
@@ -173,21 +200,46 @@ describe("sessions tools", () => {
|
|||||||
const waited = await waitPromise;
|
const waited = await waitPromise;
|
||||||
expect(waited.details).toMatchObject({
|
expect(waited.details).toMatchObject({
|
||||||
status: "ok",
|
status: "ok",
|
||||||
runId: "run-1",
|
|
||||||
reply: "done",
|
reply: "done",
|
||||||
});
|
});
|
||||||
|
expect(typeof (waited.details as { runId?: string }).runId).toBe("string");
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|
||||||
const agentCalls = calls.filter((call) => call.method === "agent");
|
const agentCalls = calls.filter((call) => call.method === "agent");
|
||||||
const waitCalls = calls.filter((call) => call.method === "agent.wait");
|
const waitCalls = calls.filter((call) => call.method === "agent.wait");
|
||||||
const historyOnlyCalls = calls.filter(
|
const historyOnlyCalls = calls.filter(
|
||||||
(call) => call.method === "chat.history",
|
(call) => call.method === "chat.history",
|
||||||
);
|
);
|
||||||
expect(agentCalls).toHaveLength(2);
|
expect(agentCalls).toHaveLength(4);
|
||||||
for (const call of agentCalls) {
|
for (const call of agentCalls) {
|
||||||
expect(call.params).toMatchObject({ lane: "nested" });
|
expect(call.params).toMatchObject({ lane: "nested" });
|
||||||
}
|
}
|
||||||
expect(waitCalls).toHaveLength(1);
|
expect(
|
||||||
expect(historyOnlyCalls).toHaveLength(1);
|
agentCalls.some(
|
||||||
expect(waitCalls[0]?.params).toMatchObject({ afterMs: 1234 });
|
(call) =>
|
||||||
|
typeof (call.params as { extraSystemPrompt?: string })
|
||||||
|
?.extraSystemPrompt === "string" &&
|
||||||
|
(call.params as { extraSystemPrompt?: string })
|
||||||
|
?.extraSystemPrompt?.includes("Agent-to-agent message context"),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
agentCalls.some(
|
||||||
|
(call) =>
|
||||||
|
typeof (call.params as { extraSystemPrompt?: string })
|
||||||
|
?.extraSystemPrompt === "string" &&
|
||||||
|
(call.params as { extraSystemPrompt?: string })
|
||||||
|
?.extraSystemPrompt?.includes("Agent-to-agent post step"),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(waitCalls).toHaveLength(3);
|
||||||
|
expect(historyOnlyCalls).toHaveLength(3);
|
||||||
|
expect(
|
||||||
|
waitCalls.some(
|
||||||
|
(call) =>
|
||||||
|
typeof (call.params as { afterMs?: number })?.afterMs === "number",
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(sendCallCount).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2712,7 +2712,88 @@ function createSessionsHistoryTool(): AnyAgentTool {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSessionsSendTool(): AnyAgentTool {
|
const ANNOUNCE_SKIP_TOKEN = "ANNOUNCE_SKIP";
|
||||||
|
|
||||||
|
type AnnounceTarget = {
|
||||||
|
channel: string;
|
||||||
|
to: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveAnnounceTargetFromKey(
|
||||||
|
sessionKey: string,
|
||||||
|
): AnnounceTarget | null {
|
||||||
|
const parts = sessionKey.split(":").filter(Boolean);
|
||||||
|
if (parts.length < 3) return null;
|
||||||
|
const [surface, kind, ...rest] = parts;
|
||||||
|
if (kind !== "group" && kind !== "channel") return null;
|
||||||
|
const id = rest.join(":").trim();
|
||||||
|
if (!id) return null;
|
||||||
|
if (!surface) return null;
|
||||||
|
const channel = surface.toLowerCase();
|
||||||
|
if (channel === "discord") {
|
||||||
|
return { channel, to: `channel:${id}` };
|
||||||
|
}
|
||||||
|
if (channel === "signal") {
|
||||||
|
return { channel, to: `group:${id}` };
|
||||||
|
}
|
||||||
|
return { channel, to: id };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAgentToAgentMessageContext(params: {
|
||||||
|
requesterSessionKey?: string;
|
||||||
|
requesterSurface?: string;
|
||||||
|
targetSessionKey: string;
|
||||||
|
}) {
|
||||||
|
const lines = [
|
||||||
|
"Agent-to-agent message context:",
|
||||||
|
params.requesterSessionKey
|
||||||
|
? `Requester session: ${params.requesterSessionKey}.`
|
||||||
|
: undefined,
|
||||||
|
params.requesterSurface
|
||||||
|
? `Requester surface: ${params.requesterSurface}.`
|
||||||
|
: undefined,
|
||||||
|
`Target session: ${params.targetSessionKey}.`,
|
||||||
|
].filter(Boolean);
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAgentToAgentPostContext(params: {
|
||||||
|
requesterSessionKey?: string;
|
||||||
|
requesterSurface?: string;
|
||||||
|
targetSessionKey: string;
|
||||||
|
targetChannel?: string;
|
||||||
|
originalMessage: string;
|
||||||
|
roundOneReply?: string;
|
||||||
|
}) {
|
||||||
|
const lines = [
|
||||||
|
"Agent-to-agent post step:",
|
||||||
|
params.requesterSessionKey
|
||||||
|
? `Requester session: ${params.requesterSessionKey}.`
|
||||||
|
: undefined,
|
||||||
|
params.requesterSurface
|
||||||
|
? `Requester surface: ${params.requesterSurface}.`
|
||||||
|
: undefined,
|
||||||
|
`Target session: ${params.targetSessionKey}.`,
|
||||||
|
params.targetChannel ? `Target surface: ${params.targetChannel}.` : undefined,
|
||||||
|
`Original request: ${params.originalMessage}`,
|
||||||
|
params.roundOneReply
|
||||||
|
? `Round 1 reply: ${params.roundOneReply}`
|
||||||
|
: "Round 1 reply: (not available).",
|
||||||
|
`If you want to remain silent, reply exactly "${ANNOUNCE_SKIP_TOKEN}".`,
|
||||||
|
"Any other reply will be posted to the target channel.",
|
||||||
|
"After this reply, the agent-to-agent conversation is over.",
|
||||||
|
].filter(Boolean);
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAnnounceSkip(text?: string) {
|
||||||
|
return (text ?? "").trim() === ANNOUNCE_SKIP_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSessionsSendTool(opts?: {
|
||||||
|
agentSessionKey?: string;
|
||||||
|
agentSurface?: string;
|
||||||
|
}): AnyAgentTool {
|
||||||
return {
|
return {
|
||||||
label: "Session Send",
|
label: "Session Send",
|
||||||
name: "sessions_send",
|
name: "sessions_send",
|
||||||
@@ -2736,6 +2817,8 @@ function createSessionsSendTool(): AnyAgentTool {
|
|||||||
Number.isFinite(params.timeoutSeconds)
|
Number.isFinite(params.timeoutSeconds)
|
||||||
? Math.max(0, Math.floor(params.timeoutSeconds))
|
? Math.max(0, Math.floor(params.timeoutSeconds))
|
||||||
: 30;
|
: 30;
|
||||||
|
const timeoutMs = timeoutSeconds * 1000;
|
||||||
|
const announceTimeoutMs = timeoutSeconds === 0 ? 30_000 : timeoutMs;
|
||||||
const idempotencyKey = crypto.randomUUID();
|
const idempotencyKey = crypto.randomUUID();
|
||||||
let runId: string = idempotencyKey;
|
let runId: string = idempotencyKey;
|
||||||
const displayKey = resolveDisplaySessionKey({
|
const displayKey = resolveDisplaySessionKey({
|
||||||
@@ -2743,12 +2826,129 @@ function createSessionsSendTool(): AnyAgentTool {
|
|||||||
alias,
|
alias,
|
||||||
mainKey,
|
mainKey,
|
||||||
});
|
});
|
||||||
|
const agentMessageContext = buildAgentToAgentMessageContext({
|
||||||
|
requesterSessionKey: opts?.agentSessionKey,
|
||||||
|
requesterSurface: opts?.agentSurface,
|
||||||
|
targetSessionKey: displayKey,
|
||||||
|
});
|
||||||
const sendParams = {
|
const sendParams = {
|
||||||
message,
|
message,
|
||||||
sessionKey: resolvedKey,
|
sessionKey: resolvedKey,
|
||||||
idempotencyKey,
|
idempotencyKey,
|
||||||
deliver: false,
|
deliver: false,
|
||||||
lane: "nested",
|
lane: "nested",
|
||||||
|
extraSystemPrompt: agentMessageContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveAnnounceTarget = async (): Promise<AnnounceTarget | null> => {
|
||||||
|
const parsed = resolveAnnounceTargetFromKey(resolvedKey);
|
||||||
|
if (parsed) return parsed;
|
||||||
|
try {
|
||||||
|
const list = (await callGateway({
|
||||||
|
method: "sessions.list",
|
||||||
|
params: {
|
||||||
|
includeGlobal: true,
|
||||||
|
includeUnknown: true,
|
||||||
|
limit: 200,
|
||||||
|
},
|
||||||
|
})) as { sessions?: Array<Record<string, unknown>> };
|
||||||
|
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
||||||
|
const match =
|
||||||
|
sessions.find((entry) => entry?.key === resolvedKey) ??
|
||||||
|
sessions.find((entry) => entry?.key === displayKey);
|
||||||
|
const channel =
|
||||||
|
typeof match?.lastChannel === "string"
|
||||||
|
? match.lastChannel
|
||||||
|
: undefined;
|
||||||
|
const to =
|
||||||
|
typeof match?.lastTo === "string" ? match.lastTo : undefined;
|
||||||
|
if (channel && to) return { channel, to };
|
||||||
|
} catch {
|
||||||
|
// ignore; fall through to null
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const runAgentToAgentPost = async (roundOneReply?: string) => {
|
||||||
|
const announceTarget = await resolveAnnounceTarget();
|
||||||
|
try {
|
||||||
|
const postPrompt = buildAgentToAgentPostContext({
|
||||||
|
requesterSessionKey: opts?.agentSessionKey,
|
||||||
|
requesterSurface: opts?.agentSurface,
|
||||||
|
targetSessionKey: displayKey,
|
||||||
|
targetChannel: announceTarget?.channel ?? "unknown",
|
||||||
|
originalMessage: message,
|
||||||
|
roundOneReply,
|
||||||
|
});
|
||||||
|
const postIdem = crypto.randomUUID();
|
||||||
|
const postResponse = (await callGateway({
|
||||||
|
method: "agent",
|
||||||
|
params: {
|
||||||
|
message: "Agent-to-agent post step.",
|
||||||
|
sessionKey: resolvedKey,
|
||||||
|
idempotencyKey: postIdem,
|
||||||
|
deliver: false,
|
||||||
|
lane: "nested",
|
||||||
|
extraSystemPrompt: postPrompt,
|
||||||
|
},
|
||||||
|
timeoutMs: 10_000,
|
||||||
|
})) as { runId?: string; acceptedAt?: number };
|
||||||
|
const postRunId =
|
||||||
|
typeof postResponse?.runId === "string" && postResponse.runId
|
||||||
|
? postResponse.runId
|
||||||
|
: postIdem;
|
||||||
|
const postAcceptedAt =
|
||||||
|
typeof postResponse?.acceptedAt === "number"
|
||||||
|
? postResponse.acceptedAt
|
||||||
|
: undefined;
|
||||||
|
const postWaitMs = Math.min(announceTimeoutMs, 60_000);
|
||||||
|
const postWait = (await callGateway({
|
||||||
|
method: "agent.wait",
|
||||||
|
params: {
|
||||||
|
runId: postRunId,
|
||||||
|
afterMs: postAcceptedAt,
|
||||||
|
timeoutMs: postWaitMs,
|
||||||
|
},
|
||||||
|
timeoutMs: postWaitMs + 2000,
|
||||||
|
})) as { status?: string };
|
||||||
|
if (postWait?.status === "ok") {
|
||||||
|
const postHistory = (await callGateway({
|
||||||
|
method: "chat.history",
|
||||||
|
params: { sessionKey: resolvedKey, limit: 50 },
|
||||||
|
})) as { messages?: unknown[] };
|
||||||
|
const postFiltered = stripToolMessages(
|
||||||
|
Array.isArray(postHistory?.messages)
|
||||||
|
? postHistory.messages
|
||||||
|
: [],
|
||||||
|
);
|
||||||
|
const postLast =
|
||||||
|
postFiltered.length > 0
|
||||||
|
? postFiltered[postFiltered.length - 1]
|
||||||
|
: undefined;
|
||||||
|
const postReply = postLast
|
||||||
|
? extractAssistantText(postLast)
|
||||||
|
: undefined;
|
||||||
|
if (
|
||||||
|
announceTarget &&
|
||||||
|
postReply &&
|
||||||
|
postReply.trim() &&
|
||||||
|
!isAnnounceSkip(postReply)
|
||||||
|
) {
|
||||||
|
await callGateway({
|
||||||
|
method: "send",
|
||||||
|
params: {
|
||||||
|
to: announceTarget.to,
|
||||||
|
message: postReply.trim(),
|
||||||
|
provider: announceTarget.channel,
|
||||||
|
idempotencyKey: crypto.randomUUID(),
|
||||||
|
},
|
||||||
|
timeoutMs: 10_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Best-effort announce; ignore failures to avoid breaking the caller response.
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (timeoutSeconds === 0) {
|
if (timeoutSeconds === 0) {
|
||||||
@@ -2761,6 +2961,7 @@ function createSessionsSendTool(): AnyAgentTool {
|
|||||||
if (typeof response?.runId === "string" && response.runId) {
|
if (typeof response?.runId === "string" && response.runId) {
|
||||||
runId = response.runId;
|
runId = response.runId;
|
||||||
}
|
}
|
||||||
|
void runAgentToAgentPost();
|
||||||
return jsonResult({
|
return jsonResult({
|
||||||
runId,
|
runId,
|
||||||
status: "accepted",
|
status: "accepted",
|
||||||
@@ -2810,7 +3011,6 @@ function createSessionsSendTool(): AnyAgentTool {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeoutMs = timeoutSeconds * 1000;
|
|
||||||
let waitStatus: string | undefined;
|
let waitStatus: string | undefined;
|
||||||
let waitError: string | undefined;
|
let waitError: string | undefined;
|
||||||
try {
|
try {
|
||||||
@@ -2867,6 +3067,7 @@ function createSessionsSendTool(): AnyAgentTool {
|
|||||||
const last =
|
const last =
|
||||||
filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
||||||
const reply = last ? extractAssistantText(last) : undefined;
|
const reply = last ? extractAssistantText(last) : undefined;
|
||||||
|
void runAgentToAgentPost(reply ?? undefined);
|
||||||
|
|
||||||
return jsonResult({
|
return jsonResult({
|
||||||
runId,
|
runId,
|
||||||
@@ -2880,6 +3081,8 @@ function createSessionsSendTool(): AnyAgentTool {
|
|||||||
|
|
||||||
export function createClawdisTools(options?: {
|
export function createClawdisTools(options?: {
|
||||||
browserControlUrl?: string;
|
browserControlUrl?: string;
|
||||||
|
agentSessionKey?: string;
|
||||||
|
agentSurface?: string;
|
||||||
}): AnyAgentTool[] {
|
}): AnyAgentTool[] {
|
||||||
return [
|
return [
|
||||||
createBrowserTool({ defaultControlUrl: options?.browserControlUrl }),
|
createBrowserTool({ defaultControlUrl: options?.browserControlUrl }),
|
||||||
@@ -2890,6 +3093,9 @@ export function createClawdisTools(options?: {
|
|||||||
createGatewayTool(),
|
createGatewayTool(),
|
||||||
createSessionsListTool(),
|
createSessionsListTool(),
|
||||||
createSessionsHistoryTool(),
|
createSessionsHistoryTool(),
|
||||||
createSessionsSendTool(),
|
createSessionsSendTool({
|
||||||
|
agentSessionKey: options?.agentSessionKey,
|
||||||
|
agentSurface: options?.agentSurface,
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -498,6 +498,7 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
bash: params.config?.agent?.bash,
|
bash: params.config?.agent?.bash,
|
||||||
sandbox,
|
sandbox,
|
||||||
surface: params.surface,
|
surface: params.surface,
|
||||||
|
sessionKey: params.sessionKey ?? params.sessionId,
|
||||||
});
|
});
|
||||||
const machineName = await getMachineDisplayName();
|
const machineName = await getMachineDisplayName();
|
||||||
const runtimeInfo = {
|
const runtimeInfo = {
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ export function createClawdisCodingTools(options?: {
|
|||||||
bash?: BashToolDefaults & ProcessToolDefaults;
|
bash?: BashToolDefaults & ProcessToolDefaults;
|
||||||
surface?: string;
|
surface?: string;
|
||||||
sandbox?: SandboxContext | null;
|
sandbox?: SandboxContext | null;
|
||||||
|
sessionKey?: string;
|
||||||
}): AnyAgentTool[] {
|
}): AnyAgentTool[] {
|
||||||
const bashToolName = "bash";
|
const bashToolName = "bash";
|
||||||
const sandbox = options?.sandbox?.enabled ? options.sandbox : undefined;
|
const sandbox = options?.sandbox?.enabled ? options.sandbox : undefined;
|
||||||
@@ -488,6 +489,8 @@ export function createClawdisCodingTools(options?: {
|
|||||||
createWhatsAppLoginTool(),
|
createWhatsAppLoginTool(),
|
||||||
...createClawdisTools({
|
...createClawdisTools({
|
||||||
browserControlUrl: sandbox?.browser?.controlUrl,
|
browserControlUrl: sandbox?.browser?.controlUrl,
|
||||||
|
agentSessionKey: options?.sessionKey,
|
||||||
|
agentSurface: options?.surface,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const allowDiscord = shouldIncludeDiscordTool(options?.surface);
|
const allowDiscord = shouldIncludeDiscordTool(options?.surface);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ type AgentCommandOpts = {
|
|||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
lane?: string;
|
lane?: string;
|
||||||
runId?: string;
|
runId?: string;
|
||||||
|
extraSystemPrompt?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SessionResolution = {
|
type SessionResolution = {
|
||||||
@@ -388,6 +389,7 @@ export async function agentCommand(
|
|||||||
runId,
|
runId,
|
||||||
lane: opts.lane,
|
lane: opts.lane,
|
||||||
abortSignal: opts.abortSignal,
|
abortSignal: opts.abortSignal,
|
||||||
|
extraSystemPrompt: opts.extraSystemPrompt,
|
||||||
onAgentEvent: (evt) => {
|
onAgentEvent: (evt) => {
|
||||||
emitAgentEvent({
|
emitAgentEvent({
|
||||||
runId,
|
runId,
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export const AgentParamsSchema = Type.Object(
|
|||||||
channel: Type.Optional(Type.String()),
|
channel: Type.Optional(Type.String()),
|
||||||
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||||
lane: Type.Optional(Type.String()),
|
lane: Type.Optional(Type.String()),
|
||||||
|
extraSystemPrompt: Type.Optional(Type.String()),
|
||||||
idempotencyKey: NonEmptyString,
|
idempotencyKey: NonEmptyString,
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
|
|||||||
@@ -2931,6 +2931,7 @@ export async function handleGatewayRequest(
|
|||||||
deliver?: boolean;
|
deliver?: boolean;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
lane?: string;
|
lane?: string;
|
||||||
|
extraSystemPrompt?: string;
|
||||||
idempotencyKey: string;
|
idempotencyKey: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
};
|
};
|
||||||
@@ -3122,6 +3123,7 @@ export async function handleGatewayRequest(
|
|||||||
surface: "VoiceWake",
|
surface: "VoiceWake",
|
||||||
runId,
|
runId,
|
||||||
lane: params.lane,
|
lane: params.lane,
|
||||||
|
extraSystemPrompt: params.extraSystemPrompt,
|
||||||
},
|
},
|
||||||
defaultRuntime,
|
defaultRuntime,
|
||||||
deps,
|
deps,
|
||||||
|
|||||||
Reference in New Issue
Block a user