@posthog/agent 2.1.115 → 2.1.120
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/agent.js +122 -33
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.d.ts +1 -0
- package/dist/posthog-api.js +11 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +7 -0
- package/dist/server/agent-server.js +317 -44
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +317 -44
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +3 -3
- package/src/adapters/acp-connection.ts +1 -1
- package/src/adapters/claude/claude-agent.ts +47 -29
- package/src/adapters/claude/conversion/acp-to-sdk.ts +6 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +7 -1
- package/src/adapters/claude/types.ts +1 -0
- package/src/posthog-api.ts +15 -0
- package/src/server/agent-server.test.ts +109 -0
- package/src/server/agent-server.ts +261 -12
- package/src/server/question-relay.test.ts +343 -0
- package/src/session-log-writer.test.ts +19 -0
- package/src/session-log-writer.ts +72 -12
- package/src/test/mocks/msw-handlers.ts +25 -1
|
@@ -908,7 +908,7 @@ import { Hono } from "hono";
|
|
|
908
908
|
// package.json
|
|
909
909
|
var package_default = {
|
|
910
910
|
name: "@posthog/agent",
|
|
911
|
-
version: "2.1.
|
|
911
|
+
version: "2.1.120",
|
|
912
912
|
repository: "https://github.com/PostHog/twig",
|
|
913
913
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
914
914
|
exports: {
|
|
@@ -1528,6 +1528,10 @@ ${chunk.resource.text}
|
|
|
1528
1528
|
function promptToClaude(prompt) {
|
|
1529
1529
|
const content = [];
|
|
1530
1530
|
const context = [];
|
|
1531
|
+
const prContext = prompt._meta?.prContext;
|
|
1532
|
+
if (typeof prContext === "string") {
|
|
1533
|
+
content.push(sdkText(prContext));
|
|
1534
|
+
}
|
|
1531
1535
|
for (const chunk of prompt.prompt) {
|
|
1532
1536
|
processPromptChunk(chunk, content, context);
|
|
1533
1537
|
}
|
|
@@ -2974,9 +2978,10 @@ async function handleAskUserQuestionTool(context) {
|
|
|
2974
2978
|
}
|
|
2975
2979
|
});
|
|
2976
2980
|
if (response.outcome?.outcome !== "selected") {
|
|
2981
|
+
const customMessage = response._meta?.message;
|
|
2977
2982
|
return {
|
|
2978
2983
|
behavior: "deny",
|
|
2979
|
-
message: "User cancelled the questions",
|
|
2984
|
+
message: typeof customMessage === "string" ? customMessage : "User cancelled the questions",
|
|
2980
2985
|
interrupt: true
|
|
2981
2986
|
};
|
|
2982
2987
|
}
|
|
@@ -3361,12 +3366,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3361
3366
|
toolUseCache;
|
|
3362
3367
|
backgroundTerminals = {};
|
|
3363
3368
|
clientCapabilities;
|
|
3364
|
-
logWriter;
|
|
3365
3369
|
options;
|
|
3366
3370
|
lastSentConfigOptions;
|
|
3367
|
-
constructor(client,
|
|
3371
|
+
constructor(client, options) {
|
|
3368
3372
|
super(client);
|
|
3369
|
-
this.logWriter = logWriter;
|
|
3370
3373
|
this.options = options;
|
|
3371
3374
|
this.toolUseCache = {};
|
|
3372
3375
|
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
|
|
@@ -3411,7 +3414,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3411
3414
|
async newSession(params) {
|
|
3412
3415
|
this.checkAuthStatus();
|
|
3413
3416
|
const meta = params._meta;
|
|
3417
|
+
const taskId = meta?.persistence?.taskId;
|
|
3414
3418
|
const sessionId = uuidv7();
|
|
3419
|
+
this.logger.info("Creating new session", {
|
|
3420
|
+
sessionId,
|
|
3421
|
+
taskId,
|
|
3422
|
+
taskRunId: meta?.taskRunId,
|
|
3423
|
+
cwd: params.cwd
|
|
3424
|
+
});
|
|
3415
3425
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3416
3426
|
const mcpServers = parseMcpServers(params);
|
|
3417
3427
|
const options = buildSessionOptions({
|
|
@@ -3440,7 +3450,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3440
3450
|
options.abortController
|
|
3441
3451
|
);
|
|
3442
3452
|
session.taskRunId = meta?.taskRunId;
|
|
3443
|
-
this.registerPersistence(sessionId, meta);
|
|
3444
3453
|
if (meta?.taskRunId) {
|
|
3445
3454
|
await this.client.extNotification("_posthog/sdk_session", {
|
|
3446
3455
|
taskRunId: meta.taskRunId,
|
|
@@ -3466,6 +3475,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3466
3475
|
}
|
|
3467
3476
|
async resumeSession(params) {
|
|
3468
3477
|
const meta = params._meta;
|
|
3478
|
+
const taskId = meta?.persistence?.taskId;
|
|
3469
3479
|
const sessionId = meta?.sessionId;
|
|
3470
3480
|
if (!sessionId) {
|
|
3471
3481
|
throw new Error("Cannot resume session without sessionId");
|
|
@@ -3473,6 +3483,12 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3473
3483
|
if (this.sessionId === sessionId) {
|
|
3474
3484
|
return {};
|
|
3475
3485
|
}
|
|
3486
|
+
this.logger.info("Resuming session", {
|
|
3487
|
+
sessionId,
|
|
3488
|
+
taskId,
|
|
3489
|
+
taskRunId: meta?.taskRunId,
|
|
3490
|
+
cwd: params.cwd
|
|
3491
|
+
});
|
|
3476
3492
|
const mcpServers = parseMcpServers(params);
|
|
3477
3493
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3478
3494
|
const { query: q, session } = await this.initializeQuery({
|
|
@@ -3485,15 +3501,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3485
3501
|
isResume: true,
|
|
3486
3502
|
additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
|
|
3487
3503
|
});
|
|
3504
|
+
this.logger.info("Session query initialized, awaiting resumption", {
|
|
3505
|
+
sessionId,
|
|
3506
|
+
taskId,
|
|
3507
|
+
taskRunId: meta?.taskRunId
|
|
3508
|
+
});
|
|
3488
3509
|
session.taskRunId = meta?.taskRunId;
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3510
|
+
try {
|
|
3511
|
+
const result = await withTimeout(
|
|
3512
|
+
q.initializationResult(),
|
|
3513
|
+
SESSION_VALIDATION_TIMEOUT_MS
|
|
3514
|
+
);
|
|
3515
|
+
if (result.result === "timeout") {
|
|
3516
|
+
throw new Error(
|
|
3517
|
+
`Session resumption timed out for sessionId=${sessionId}`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
} catch (err) {
|
|
3521
|
+
this.logger.error("Session resumption failed", {
|
|
3522
|
+
sessionId,
|
|
3523
|
+
taskId,
|
|
3524
|
+
taskRunId: meta?.taskRunId,
|
|
3525
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3526
|
+
});
|
|
3527
|
+
throw err;
|
|
3496
3528
|
}
|
|
3529
|
+
this.logger.info("Session resumed successfully", {
|
|
3530
|
+
sessionId,
|
|
3531
|
+
taskId,
|
|
3532
|
+
taskRunId: meta?.taskRunId
|
|
3533
|
+
});
|
|
3497
3534
|
this.deferBackgroundFetches(q, sessionId);
|
|
3498
3535
|
const configOptions = await this.buildConfigOptions();
|
|
3499
3536
|
return { configOptions };
|
|
@@ -3692,12 +3729,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3692
3729
|
this.logger.warn("Failed to fetch deferred session data", { err });
|
|
3693
3730
|
});
|
|
3694
3731
|
}
|
|
3695
|
-
registerPersistence(sessionId, meta) {
|
|
3696
|
-
const persistence = meta?.persistence;
|
|
3697
|
-
if (persistence && this.logWriter) {
|
|
3698
|
-
this.logWriter.register(sessionId, persistence);
|
|
3699
|
-
}
|
|
3700
|
-
}
|
|
3701
3732
|
sendAvailableCommandsUpdate(sessionId, availableCommands) {
|
|
3702
3733
|
setTimeout(() => {
|
|
3703
3734
|
this.client.sessionUpdate({
|
|
@@ -3985,7 +4016,7 @@ function createClaudeConnection(config) {
|
|
|
3985
4016
|
const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
|
|
3986
4017
|
let agent = null;
|
|
3987
4018
|
const agentConnection = new AgentSideConnection((client) => {
|
|
3988
|
-
agent = new ClaudeAcpAgent(client,
|
|
4019
|
+
agent = new ClaudeAcpAgent(client, config.processCallbacks);
|
|
3989
4020
|
logger.info(`Created ${agent.adapterName} agent`);
|
|
3990
4021
|
return agent;
|
|
3991
4022
|
}, agentStream);
|
|
@@ -4320,6 +4351,16 @@ var PostHogAPIClient = class {
|
|
|
4320
4351
|
}
|
|
4321
4352
|
);
|
|
4322
4353
|
}
|
|
4354
|
+
async relayMessage(taskId, runId, text2) {
|
|
4355
|
+
const teamId = this.getTeamId();
|
|
4356
|
+
await this.apiRequest(
|
|
4357
|
+
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
|
|
4358
|
+
{
|
|
4359
|
+
method: "POST",
|
|
4360
|
+
body: JSON.stringify({ text: text2 })
|
|
4361
|
+
}
|
|
4362
|
+
);
|
|
4363
|
+
}
|
|
4323
4364
|
async uploadTaskArtifacts(taskId, runId, artifacts) {
|
|
4324
4365
|
if (!artifacts.length) {
|
|
4325
4366
|
return [];
|
|
@@ -4425,11 +4466,15 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4425
4466
|
}
|
|
4426
4467
|
async flushAll() {
|
|
4427
4468
|
const sessionIds = [...this.sessions.keys()];
|
|
4428
|
-
const pendingCounts = sessionIds.map((id) =>
|
|
4429
|
-
id
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4469
|
+
const pendingCounts = sessionIds.map((id) => {
|
|
4470
|
+
const session = this.sessions.get(id);
|
|
4471
|
+
return {
|
|
4472
|
+
taskId: session?.context.taskId,
|
|
4473
|
+
runId: session?.context.runId,
|
|
4474
|
+
pending: this.pendingEntries.get(id)?.length ?? 0,
|
|
4475
|
+
messages: this.messageCounts.get(id) ?? 0
|
|
4476
|
+
};
|
|
4477
|
+
});
|
|
4433
4478
|
this.logger.info("flushAll called", {
|
|
4434
4479
|
sessions: sessionIds.length,
|
|
4435
4480
|
pending: pendingCounts
|
|
@@ -4445,8 +4490,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4445
4490
|
return;
|
|
4446
4491
|
}
|
|
4447
4492
|
this.logger.info("Session registered", {
|
|
4448
|
-
|
|
4449
|
-
|
|
4493
|
+
taskId: context.taskId,
|
|
4494
|
+
runId: context.runId
|
|
4450
4495
|
});
|
|
4451
4496
|
this.sessions.set(sessionId, { context });
|
|
4452
4497
|
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
@@ -4480,7 +4525,11 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4480
4525
|
const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
|
|
4481
4526
|
this.messageCounts.set(sessionId, count);
|
|
4482
4527
|
if (count % 10 === 1) {
|
|
4483
|
-
this.logger.info("Messages received", {
|
|
4528
|
+
this.logger.info("Messages received", {
|
|
4529
|
+
count,
|
|
4530
|
+
taskId: session.context.taskId,
|
|
4531
|
+
runId: session.context.runId
|
|
4532
|
+
});
|
|
4484
4533
|
}
|
|
4485
4534
|
try {
|
|
4486
4535
|
const message = JSON.parse(line);
|
|
@@ -4497,6 +4546,10 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4497
4546
|
return;
|
|
4498
4547
|
}
|
|
4499
4548
|
this.emitCoalescedMessage(sessionId, session);
|
|
4549
|
+
const nonChunkAgentText = this.extractAgentMessageText(message);
|
|
4550
|
+
if (nonChunkAgentText) {
|
|
4551
|
+
session.lastAgentMessage = nonChunkAgentText;
|
|
4552
|
+
}
|
|
4500
4553
|
const entry = {
|
|
4501
4554
|
type: "notification",
|
|
4502
4555
|
timestamp,
|
|
@@ -4511,7 +4564,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4511
4564
|
}
|
|
4512
4565
|
} catch {
|
|
4513
4566
|
this.logger.warn("Failed to parse raw line for persistence", {
|
|
4514
|
-
|
|
4567
|
+
taskId: session.context.taskId,
|
|
4568
|
+
runId: session.context.runId,
|
|
4515
4569
|
lineLength: line.length
|
|
4516
4570
|
});
|
|
4517
4571
|
}
|
|
@@ -4526,7 +4580,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4526
4580
|
const pending = this.pendingEntries.get(sessionId);
|
|
4527
4581
|
if (!this.posthogAPI || !pending?.length) {
|
|
4528
4582
|
this.logger.info("flush: nothing to persist", {
|
|
4529
|
-
|
|
4583
|
+
taskId: session.context.taskId,
|
|
4584
|
+
runId: session.context.runId,
|
|
4530
4585
|
hasPosthogAPI: !!this.posthogAPI,
|
|
4531
4586
|
pendingCount: pending?.length ?? 0
|
|
4532
4587
|
});
|
|
@@ -4547,7 +4602,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4547
4602
|
);
|
|
4548
4603
|
this.retryCounts.set(sessionId, 0);
|
|
4549
4604
|
this.logger.info("Flushed session logs", {
|
|
4550
|
-
|
|
4605
|
+
taskId: session.context.taskId,
|
|
4606
|
+
runId: session.context.runId,
|
|
4551
4607
|
entryCount: pending.length
|
|
4552
4608
|
});
|
|
4553
4609
|
} catch (error) {
|
|
@@ -4556,7 +4612,11 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4556
4612
|
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
4557
4613
|
this.logger.error(
|
|
4558
4614
|
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
4559
|
-
{
|
|
4615
|
+
{
|
|
4616
|
+
taskId: session.context.taskId,
|
|
4617
|
+
runId: session.context.runId,
|
|
4618
|
+
error
|
|
4619
|
+
}
|
|
4560
4620
|
);
|
|
4561
4621
|
this.retryCounts.set(sessionId, 0);
|
|
4562
4622
|
} else {
|
|
@@ -4589,6 +4649,7 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4589
4649
|
if (!session.chunkBuffer) return;
|
|
4590
4650
|
const { text: text2, firstTimestamp } = session.chunkBuffer;
|
|
4591
4651
|
session.chunkBuffer = void 0;
|
|
4652
|
+
session.lastAgentMessage = text2;
|
|
4592
4653
|
const entry = {
|
|
4593
4654
|
type: "notification",
|
|
4594
4655
|
timestamp: firstTimestamp,
|
|
@@ -4611,6 +4672,29 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4611
4672
|
this.scheduleFlush(sessionId);
|
|
4612
4673
|
}
|
|
4613
4674
|
}
|
|
4675
|
+
getLastAgentMessage(sessionId) {
|
|
4676
|
+
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
4677
|
+
}
|
|
4678
|
+
extractAgentMessageText(message) {
|
|
4679
|
+
if (message.method !== "session/update") {
|
|
4680
|
+
return null;
|
|
4681
|
+
}
|
|
4682
|
+
const params = message.params;
|
|
4683
|
+
const update = params?.update;
|
|
4684
|
+
if (update?.sessionUpdate !== "agent_message") {
|
|
4685
|
+
return null;
|
|
4686
|
+
}
|
|
4687
|
+
const content = update.content;
|
|
4688
|
+
if (content?.type === "text" && typeof content.text === "string") {
|
|
4689
|
+
const trimmed2 = content.text.trim();
|
|
4690
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
4691
|
+
}
|
|
4692
|
+
if (typeof update.message === "string") {
|
|
4693
|
+
const trimmed2 = update.message.trim();
|
|
4694
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
4695
|
+
}
|
|
4696
|
+
return null;
|
|
4697
|
+
}
|
|
4614
4698
|
scheduleFlush(sessionId) {
|
|
4615
4699
|
const existing = this.flushTimeouts.get(sessionId);
|
|
4616
4700
|
if (existing) clearTimeout(existing);
|
|
@@ -4645,7 +4729,12 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4645
4729
|
fs3.appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
4646
4730
|
`);
|
|
4647
4731
|
} catch (error) {
|
|
4648
|
-
this.logger.warn("Failed to write to local cache", {
|
|
4732
|
+
this.logger.warn("Failed to write to local cache", {
|
|
4733
|
+
taskId: session.context.taskId,
|
|
4734
|
+
runId: session.context.runId,
|
|
4735
|
+
logPath,
|
|
4736
|
+
error
|
|
4737
|
+
});
|
|
4649
4738
|
}
|
|
4650
4739
|
}
|
|
4651
4740
|
};
|
|
@@ -10278,6 +10367,8 @@ var AgentServer = class {
|
|
|
10278
10367
|
session = null;
|
|
10279
10368
|
app;
|
|
10280
10369
|
posthogAPI;
|
|
10370
|
+
questionRelayedToSlack = false;
|
|
10371
|
+
detectedPrUrl = null;
|
|
10281
10372
|
constructor(config) {
|
|
10282
10373
|
this.config = config;
|
|
10283
10374
|
this.logger = new Logger({ debug: true, prefix: "[AgentServer]" });
|
|
@@ -10490,11 +10581,21 @@ var AgentServer = class {
|
|
|
10490
10581
|
case "user_message": {
|
|
10491
10582
|
const content = params.content;
|
|
10492
10583
|
this.logger.info(
|
|
10493
|
-
`Processing user message: ${content.substring(0, 100)}...`
|
|
10584
|
+
`Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${content.substring(0, 100)}...`
|
|
10494
10585
|
);
|
|
10495
10586
|
const result = await this.session.clientConnection.prompt({
|
|
10496
10587
|
sessionId: this.session.acpSessionId,
|
|
10497
|
-
prompt: [{ type: "text", text: content }]
|
|
10588
|
+
prompt: [{ type: "text", text: content }],
|
|
10589
|
+
...this.detectedPrUrl && {
|
|
10590
|
+
_meta: {
|
|
10591
|
+
prContext: `IMPORTANT \u2014 OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.
|
|
10592
|
+
You already have an open pull request: ${this.detectedPrUrl}
|
|
10593
|
+
You MUST:
|
|
10594
|
+
1. Check out the existing PR branch with \`gh pr checkout ${this.detectedPrUrl}\`
|
|
10595
|
+
2. Make changes, commit, and push to that branch
|
|
10596
|
+
You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
10597
|
+
}
|
|
10598
|
+
}
|
|
10498
10599
|
});
|
|
10499
10600
|
return { stopReason: result.stopReason };
|
|
10500
10601
|
}
|
|
@@ -10579,13 +10680,29 @@ var AgentServer = class {
|
|
|
10579
10680
|
protocolVersion: PROTOCOL_VERSION,
|
|
10580
10681
|
clientCapabilities: {}
|
|
10581
10682
|
});
|
|
10683
|
+
let preTaskRun = null;
|
|
10684
|
+
try {
|
|
10685
|
+
preTaskRun = await this.posthogAPI.getTaskRun(
|
|
10686
|
+
payload.task_id,
|
|
10687
|
+
payload.run_id
|
|
10688
|
+
);
|
|
10689
|
+
} catch {
|
|
10690
|
+
this.logger.warn("Failed to fetch task run for session context", {
|
|
10691
|
+
taskId: payload.task_id,
|
|
10692
|
+
runId: payload.run_id
|
|
10693
|
+
});
|
|
10694
|
+
}
|
|
10695
|
+
const prUrl = typeof preTaskRun?.state?.slack_notified_pr_url === "string" ? (preTaskRun?.state).slack_notified_pr_url : null;
|
|
10696
|
+
if (prUrl) {
|
|
10697
|
+
this.detectedPrUrl = prUrl;
|
|
10698
|
+
}
|
|
10582
10699
|
const sessionResponse = await clientConnection.newSession({
|
|
10583
10700
|
cwd: this.config.repositoryPath,
|
|
10584
10701
|
mcpServers: [],
|
|
10585
10702
|
_meta: {
|
|
10586
10703
|
sessionId: payload.run_id,
|
|
10587
10704
|
taskRunId: payload.run_id,
|
|
10588
|
-
systemPrompt: { append: this.buildCloudSystemPrompt() }
|
|
10705
|
+
systemPrompt: { append: this.buildCloudSystemPrompt(prUrl) }
|
|
10589
10706
|
}
|
|
10590
10707
|
});
|
|
10591
10708
|
const acpSessionId = sessionResponse.sessionId;
|
|
@@ -10609,28 +10726,51 @@ var AgentServer = class {
|
|
|
10609
10726
|
}).catch(
|
|
10610
10727
|
(err) => this.logger.warn("Failed to set task run to in_progress", err)
|
|
10611
10728
|
);
|
|
10612
|
-
await this.sendInitialTaskMessage(payload);
|
|
10729
|
+
await this.sendInitialTaskMessage(payload, preTaskRun);
|
|
10613
10730
|
}
|
|
10614
|
-
async sendInitialTaskMessage(payload) {
|
|
10731
|
+
async sendInitialTaskMessage(payload, prefetchedRun) {
|
|
10615
10732
|
if (!this.session) return;
|
|
10616
10733
|
try {
|
|
10617
|
-
this.logger.info("Fetching task details", { taskId: payload.task_id });
|
|
10618
10734
|
const task = await this.posthogAPI.getTask(payload.task_id);
|
|
10619
|
-
|
|
10735
|
+
let taskRun = prefetchedRun ?? null;
|
|
10736
|
+
if (!taskRun) {
|
|
10737
|
+
try {
|
|
10738
|
+
taskRun = await this.posthogAPI.getTaskRun(
|
|
10739
|
+
payload.task_id,
|
|
10740
|
+
payload.run_id
|
|
10741
|
+
);
|
|
10742
|
+
} catch (error) {
|
|
10743
|
+
this.logger.warn(
|
|
10744
|
+
"Failed to fetch task run for initial prompt override",
|
|
10745
|
+
{
|
|
10746
|
+
taskId: payload.task_id,
|
|
10747
|
+
runId: payload.run_id,
|
|
10748
|
+
error
|
|
10749
|
+
}
|
|
10750
|
+
);
|
|
10751
|
+
}
|
|
10752
|
+
}
|
|
10753
|
+
const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
|
|
10754
|
+
const initialPrompt = initialPromptOverride ?? task.description;
|
|
10755
|
+
if (!initialPrompt) {
|
|
10620
10756
|
this.logger.warn("Task has no description, skipping initial message");
|
|
10621
10757
|
return;
|
|
10622
10758
|
}
|
|
10623
10759
|
this.logger.info("Sending initial task message", {
|
|
10624
10760
|
taskId: payload.task_id,
|
|
10625
|
-
descriptionLength:
|
|
10761
|
+
descriptionLength: initialPrompt.length,
|
|
10762
|
+
usedInitialPromptOverride: !!initialPromptOverride
|
|
10626
10763
|
});
|
|
10627
10764
|
const result = await this.session.clientConnection.prompt({
|
|
10628
10765
|
sessionId: this.session.acpSessionId,
|
|
10629
|
-
prompt: [{ type: "text", text:
|
|
10766
|
+
prompt: [{ type: "text", text: initialPrompt }]
|
|
10630
10767
|
});
|
|
10631
10768
|
this.logger.info("Initial task message completed", {
|
|
10632
10769
|
stopReason: result.stopReason
|
|
10633
10770
|
});
|
|
10771
|
+
if (result.stopReason === "end_turn") {
|
|
10772
|
+
await this.relayAgentResponse(payload);
|
|
10773
|
+
}
|
|
10634
10774
|
} catch (error) {
|
|
10635
10775
|
this.logger.error("Failed to send initial task message", error);
|
|
10636
10776
|
if (this.session) {
|
|
@@ -10639,7 +10779,33 @@ var AgentServer = class {
|
|
|
10639
10779
|
await this.signalTaskComplete(payload, "error");
|
|
10640
10780
|
}
|
|
10641
10781
|
}
|
|
10642
|
-
|
|
10782
|
+
getInitialPromptOverride(taskRun) {
|
|
10783
|
+
const state = taskRun.state;
|
|
10784
|
+
const override = state?.initial_prompt_override;
|
|
10785
|
+
if (typeof override !== "string") {
|
|
10786
|
+
return null;
|
|
10787
|
+
}
|
|
10788
|
+
const trimmed2 = override.trim();
|
|
10789
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
10790
|
+
}
|
|
10791
|
+
buildCloudSystemPrompt(prUrl) {
|
|
10792
|
+
if (prUrl) {
|
|
10793
|
+
return `
|
|
10794
|
+
# Cloud Task Execution
|
|
10795
|
+
|
|
10796
|
+
This task already has an open pull request: ${prUrl}
|
|
10797
|
+
|
|
10798
|
+
After completing the requested changes:
|
|
10799
|
+
1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`
|
|
10800
|
+
2. Stage and commit all changes with a clear commit message
|
|
10801
|
+
3. Push to the existing PR branch
|
|
10802
|
+
|
|
10803
|
+
Important:
|
|
10804
|
+
- Do NOT create a new branch or a new pull request.
|
|
10805
|
+
- Do NOT add "Co-Authored-By" trailers to commit messages.
|
|
10806
|
+
- Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
|
|
10807
|
+
`;
|
|
10808
|
+
}
|
|
10643
10809
|
return `
|
|
10644
10810
|
# Cloud Task Execution
|
|
10645
10811
|
|
|
@@ -10709,19 +10875,34 @@ Important:
|
|
|
10709
10875
|
}
|
|
10710
10876
|
createCloudClient(payload) {
|
|
10711
10877
|
const mode = this.getEffectiveMode(payload);
|
|
10878
|
+
const interactionOrigin = process.env.TWIG_INTERACTION_ORIGIN;
|
|
10712
10879
|
return {
|
|
10713
10880
|
requestPermission: async (params) => {
|
|
10714
10881
|
this.logger.debug("Permission request", {
|
|
10715
10882
|
mode,
|
|
10883
|
+
interactionOrigin,
|
|
10716
10884
|
options: params.options
|
|
10717
10885
|
});
|
|
10718
10886
|
const allowOption = params.options.find(
|
|
10719
10887
|
(o) => o.kind === "allow_once" || o.kind === "allow_always"
|
|
10720
10888
|
);
|
|
10889
|
+
const selectedOptionId = allowOption?.optionId ?? params.options[0].optionId;
|
|
10890
|
+
if (interactionOrigin === "slack") {
|
|
10891
|
+
const twigToolKind = params.toolCall?._meta?.twigToolKind;
|
|
10892
|
+
if (twigToolKind === "question") {
|
|
10893
|
+
this.relaySlackQuestion(payload, params.toolCall?._meta);
|
|
10894
|
+
return {
|
|
10895
|
+
outcome: { outcome: "cancelled" },
|
|
10896
|
+
_meta: {
|
|
10897
|
+
message: "This question has been relayed to the Slack thread where this task originated. The user will reply there. Do NOT re-ask the question or pick an answer yourself. Simply let the user know you are waiting for their reply."
|
|
10898
|
+
}
|
|
10899
|
+
};
|
|
10900
|
+
}
|
|
10901
|
+
}
|
|
10721
10902
|
return {
|
|
10722
10903
|
outcome: {
|
|
10723
10904
|
outcome: "selected",
|
|
10724
|
-
optionId:
|
|
10905
|
+
optionId: selectedOptionId
|
|
10725
10906
|
}
|
|
10726
10907
|
};
|
|
10727
10908
|
},
|
|
@@ -10740,6 +10921,97 @@ Important:
|
|
|
10740
10921
|
}
|
|
10741
10922
|
};
|
|
10742
10923
|
}
|
|
10924
|
+
async relayAgentResponse(payload) {
|
|
10925
|
+
if (!this.session) {
|
|
10926
|
+
return;
|
|
10927
|
+
}
|
|
10928
|
+
if (this.questionRelayedToSlack) {
|
|
10929
|
+
this.questionRelayedToSlack = false;
|
|
10930
|
+
return;
|
|
10931
|
+
}
|
|
10932
|
+
try {
|
|
10933
|
+
await this.session.logWriter.flush(payload.run_id);
|
|
10934
|
+
} catch (error) {
|
|
10935
|
+
this.logger.warn("Failed to flush logs before Slack relay", {
|
|
10936
|
+
taskId: payload.task_id,
|
|
10937
|
+
runId: payload.run_id,
|
|
10938
|
+
error
|
|
10939
|
+
});
|
|
10940
|
+
}
|
|
10941
|
+
const message = this.session.logWriter.getLastAgentMessage(payload.run_id);
|
|
10942
|
+
if (!message) {
|
|
10943
|
+
this.logger.warn("No agent message found for Slack relay", {
|
|
10944
|
+
taskId: payload.task_id,
|
|
10945
|
+
runId: payload.run_id,
|
|
10946
|
+
sessionRegistered: this.session.logWriter.isRegistered(payload.run_id)
|
|
10947
|
+
});
|
|
10948
|
+
return;
|
|
10949
|
+
}
|
|
10950
|
+
try {
|
|
10951
|
+
await this.posthogAPI.relayMessage(
|
|
10952
|
+
payload.task_id,
|
|
10953
|
+
payload.run_id,
|
|
10954
|
+
message
|
|
10955
|
+
);
|
|
10956
|
+
} catch (error) {
|
|
10957
|
+
this.logger.warn("Failed to relay initial agent response to Slack", {
|
|
10958
|
+
taskId: payload.task_id,
|
|
10959
|
+
runId: payload.run_id,
|
|
10960
|
+
error
|
|
10961
|
+
});
|
|
10962
|
+
}
|
|
10963
|
+
}
|
|
10964
|
+
relaySlackQuestion(payload, toolMeta2) {
|
|
10965
|
+
const firstQuestion = this.getFirstQuestionMeta(toolMeta2);
|
|
10966
|
+
if (!this.isQuestionMeta(firstQuestion)) {
|
|
10967
|
+
return;
|
|
10968
|
+
}
|
|
10969
|
+
let message = `*${firstQuestion.question}*
|
|
10970
|
+
|
|
10971
|
+
`;
|
|
10972
|
+
if (firstQuestion.options?.length) {
|
|
10973
|
+
firstQuestion.options.forEach(
|
|
10974
|
+
(opt, i) => {
|
|
10975
|
+
message += `${i + 1}. *${opt.label}*`;
|
|
10976
|
+
if (opt.description) message += ` \u2014 ${opt.description}`;
|
|
10977
|
+
message += "\n";
|
|
10978
|
+
}
|
|
10979
|
+
);
|
|
10980
|
+
}
|
|
10981
|
+
message += "\nReply in this thread with your choice.";
|
|
10982
|
+
this.questionRelayedToSlack = true;
|
|
10983
|
+
this.posthogAPI.relayMessage(payload.task_id, payload.run_id, message).catch(
|
|
10984
|
+
(err) => this.logger.warn("Failed to relay question to Slack", { err })
|
|
10985
|
+
);
|
|
10986
|
+
}
|
|
10987
|
+
getFirstQuestionMeta(toolMeta2) {
|
|
10988
|
+
if (!toolMeta2) {
|
|
10989
|
+
return null;
|
|
10990
|
+
}
|
|
10991
|
+
const questionsValue = toolMeta2.questions;
|
|
10992
|
+
if (!Array.isArray(questionsValue) || questionsValue.length === 0) {
|
|
10993
|
+
return null;
|
|
10994
|
+
}
|
|
10995
|
+
return questionsValue[0];
|
|
10996
|
+
}
|
|
10997
|
+
isQuestionMeta(value) {
|
|
10998
|
+
if (!value || typeof value !== "object") {
|
|
10999
|
+
return false;
|
|
11000
|
+
}
|
|
11001
|
+
const candidate = value;
|
|
11002
|
+
if (typeof candidate.question !== "string") {
|
|
11003
|
+
return false;
|
|
11004
|
+
}
|
|
11005
|
+
if (candidate.options === void 0) {
|
|
11006
|
+
return true;
|
|
11007
|
+
}
|
|
11008
|
+
if (!Array.isArray(candidate.options)) {
|
|
11009
|
+
return false;
|
|
11010
|
+
}
|
|
11011
|
+
return candidate.options.every(
|
|
11012
|
+
(option) => !!option && typeof option === "object" && typeof option.label === "string"
|
|
11013
|
+
);
|
|
11014
|
+
}
|
|
10743
11015
|
detectAndAttachPrUrl(payload, update) {
|
|
10744
11016
|
try {
|
|
10745
11017
|
const meta = update?._meta?.claudeCode;
|
|
@@ -10770,6 +11042,7 @@ Important:
|
|
|
10770
11042
|
);
|
|
10771
11043
|
if (!prUrlMatch) return;
|
|
10772
11044
|
const prUrl = prUrlMatch[0];
|
|
11045
|
+
this.detectedPrUrl = prUrl;
|
|
10773
11046
|
this.logger.info("Detected PR URL in bash output", {
|
|
10774
11047
|
runId: payload.run_id,
|
|
10775
11048
|
prUrl
|