@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
package/dist/server/bin.cjs
CHANGED
|
@@ -904,7 +904,7 @@ var import_hono = require("hono");
|
|
|
904
904
|
// package.json
|
|
905
905
|
var package_default = {
|
|
906
906
|
name: "@posthog/agent",
|
|
907
|
-
version: "2.1.
|
|
907
|
+
version: "2.1.120",
|
|
908
908
|
repository: "https://github.com/PostHog/twig",
|
|
909
909
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
910
910
|
exports: {
|
|
@@ -1520,6 +1520,10 @@ ${chunk.resource.text}
|
|
|
1520
1520
|
function promptToClaude(prompt) {
|
|
1521
1521
|
const content = [];
|
|
1522
1522
|
const context = [];
|
|
1523
|
+
const prContext = prompt._meta?.prContext;
|
|
1524
|
+
if (typeof prContext === "string") {
|
|
1525
|
+
content.push(sdkText(prContext));
|
|
1526
|
+
}
|
|
1523
1527
|
for (const chunk of prompt.prompt) {
|
|
1524
1528
|
processPromptChunk(chunk, content, context);
|
|
1525
1529
|
}
|
|
@@ -2966,9 +2970,10 @@ async function handleAskUserQuestionTool(context) {
|
|
|
2966
2970
|
}
|
|
2967
2971
|
});
|
|
2968
2972
|
if (response.outcome?.outcome !== "selected") {
|
|
2973
|
+
const customMessage = response._meta?.message;
|
|
2969
2974
|
return {
|
|
2970
2975
|
behavior: "deny",
|
|
2971
|
-
message: "User cancelled the questions",
|
|
2976
|
+
message: typeof customMessage === "string" ? customMessage : "User cancelled the questions",
|
|
2972
2977
|
interrupt: true
|
|
2973
2978
|
};
|
|
2974
2979
|
}
|
|
@@ -3353,12 +3358,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3353
3358
|
toolUseCache;
|
|
3354
3359
|
backgroundTerminals = {};
|
|
3355
3360
|
clientCapabilities;
|
|
3356
|
-
logWriter;
|
|
3357
3361
|
options;
|
|
3358
3362
|
lastSentConfigOptions;
|
|
3359
|
-
constructor(client,
|
|
3363
|
+
constructor(client, options) {
|
|
3360
3364
|
super(client);
|
|
3361
|
-
this.logWriter = logWriter;
|
|
3362
3365
|
this.options = options;
|
|
3363
3366
|
this.toolUseCache = {};
|
|
3364
3367
|
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
|
|
@@ -3403,7 +3406,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3403
3406
|
async newSession(params) {
|
|
3404
3407
|
this.checkAuthStatus();
|
|
3405
3408
|
const meta = params._meta;
|
|
3409
|
+
const taskId = meta?.persistence?.taskId;
|
|
3406
3410
|
const sessionId = (0, import_uuid.v7)();
|
|
3411
|
+
this.logger.info("Creating new session", {
|
|
3412
|
+
sessionId,
|
|
3413
|
+
taskId,
|
|
3414
|
+
taskRunId: meta?.taskRunId,
|
|
3415
|
+
cwd: params.cwd
|
|
3416
|
+
});
|
|
3407
3417
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3408
3418
|
const mcpServers = parseMcpServers(params);
|
|
3409
3419
|
const options = buildSessionOptions({
|
|
@@ -3432,7 +3442,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3432
3442
|
options.abortController
|
|
3433
3443
|
);
|
|
3434
3444
|
session.taskRunId = meta?.taskRunId;
|
|
3435
|
-
this.registerPersistence(sessionId, meta);
|
|
3436
3445
|
if (meta?.taskRunId) {
|
|
3437
3446
|
await this.client.extNotification("_posthog/sdk_session", {
|
|
3438
3447
|
taskRunId: meta.taskRunId,
|
|
@@ -3458,6 +3467,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3458
3467
|
}
|
|
3459
3468
|
async resumeSession(params) {
|
|
3460
3469
|
const meta = params._meta;
|
|
3470
|
+
const taskId = meta?.persistence?.taskId;
|
|
3461
3471
|
const sessionId = meta?.sessionId;
|
|
3462
3472
|
if (!sessionId) {
|
|
3463
3473
|
throw new Error("Cannot resume session without sessionId");
|
|
@@ -3465,6 +3475,12 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3465
3475
|
if (this.sessionId === sessionId) {
|
|
3466
3476
|
return {};
|
|
3467
3477
|
}
|
|
3478
|
+
this.logger.info("Resuming session", {
|
|
3479
|
+
sessionId,
|
|
3480
|
+
taskId,
|
|
3481
|
+
taskRunId: meta?.taskRunId,
|
|
3482
|
+
cwd: params.cwd
|
|
3483
|
+
});
|
|
3468
3484
|
const mcpServers = parseMcpServers(params);
|
|
3469
3485
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3470
3486
|
const { query: q, session } = await this.initializeQuery({
|
|
@@ -3477,15 +3493,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3477
3493
|
isResume: true,
|
|
3478
3494
|
additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
|
|
3479
3495
|
});
|
|
3496
|
+
this.logger.info("Session query initialized, awaiting resumption", {
|
|
3497
|
+
sessionId,
|
|
3498
|
+
taskId,
|
|
3499
|
+
taskRunId: meta?.taskRunId
|
|
3500
|
+
});
|
|
3480
3501
|
session.taskRunId = meta?.taskRunId;
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3502
|
+
try {
|
|
3503
|
+
const result = await withTimeout(
|
|
3504
|
+
q.initializationResult(),
|
|
3505
|
+
SESSION_VALIDATION_TIMEOUT_MS
|
|
3506
|
+
);
|
|
3507
|
+
if (result.result === "timeout") {
|
|
3508
|
+
throw new Error(
|
|
3509
|
+
`Session resumption timed out for sessionId=${sessionId}`
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
} catch (err) {
|
|
3513
|
+
this.logger.error("Session resumption failed", {
|
|
3514
|
+
sessionId,
|
|
3515
|
+
taskId,
|
|
3516
|
+
taskRunId: meta?.taskRunId,
|
|
3517
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3518
|
+
});
|
|
3519
|
+
throw err;
|
|
3488
3520
|
}
|
|
3521
|
+
this.logger.info("Session resumed successfully", {
|
|
3522
|
+
sessionId,
|
|
3523
|
+
taskId,
|
|
3524
|
+
taskRunId: meta?.taskRunId
|
|
3525
|
+
});
|
|
3489
3526
|
this.deferBackgroundFetches(q, sessionId);
|
|
3490
3527
|
const configOptions = await this.buildConfigOptions();
|
|
3491
3528
|
return { configOptions };
|
|
@@ -3684,12 +3721,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3684
3721
|
this.logger.warn("Failed to fetch deferred session data", { err });
|
|
3685
3722
|
});
|
|
3686
3723
|
}
|
|
3687
|
-
registerPersistence(sessionId, meta) {
|
|
3688
|
-
const persistence = meta?.persistence;
|
|
3689
|
-
if (persistence && this.logWriter) {
|
|
3690
|
-
this.logWriter.register(sessionId, persistence);
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
3724
|
sendAvailableCommandsUpdate(sessionId, availableCommands) {
|
|
3694
3725
|
setTimeout(() => {
|
|
3695
3726
|
this.client.sessionUpdate({
|
|
@@ -3977,7 +4008,7 @@ function createClaudeConnection(config) {
|
|
|
3977
4008
|
const agentStream = (0, import_sdk3.ndJsonStream)(agentWritable, streams.agent.readable);
|
|
3978
4009
|
let agent = null;
|
|
3979
4010
|
const agentConnection = new import_sdk3.AgentSideConnection((client) => {
|
|
3980
|
-
agent = new ClaudeAcpAgent(client,
|
|
4011
|
+
agent = new ClaudeAcpAgent(client, config.processCallbacks);
|
|
3981
4012
|
logger.info(`Created ${agent.adapterName} agent`);
|
|
3982
4013
|
return agent;
|
|
3983
4014
|
}, agentStream);
|
|
@@ -4312,6 +4343,16 @@ var PostHogAPIClient = class {
|
|
|
4312
4343
|
}
|
|
4313
4344
|
);
|
|
4314
4345
|
}
|
|
4346
|
+
async relayMessage(taskId, runId, text2) {
|
|
4347
|
+
const teamId = this.getTeamId();
|
|
4348
|
+
await this.apiRequest(
|
|
4349
|
+
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
|
|
4350
|
+
{
|
|
4351
|
+
method: "POST",
|
|
4352
|
+
body: JSON.stringify({ text: text2 })
|
|
4353
|
+
}
|
|
4354
|
+
);
|
|
4355
|
+
}
|
|
4315
4356
|
async uploadTaskArtifacts(taskId, runId, artifacts) {
|
|
4316
4357
|
if (!artifacts.length) {
|
|
4317
4358
|
return [];
|
|
@@ -4417,11 +4458,15 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4417
4458
|
}
|
|
4418
4459
|
async flushAll() {
|
|
4419
4460
|
const sessionIds = [...this.sessions.keys()];
|
|
4420
|
-
const pendingCounts = sessionIds.map((id) =>
|
|
4421
|
-
id
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4461
|
+
const pendingCounts = sessionIds.map((id) => {
|
|
4462
|
+
const session = this.sessions.get(id);
|
|
4463
|
+
return {
|
|
4464
|
+
taskId: session?.context.taskId,
|
|
4465
|
+
runId: session?.context.runId,
|
|
4466
|
+
pending: this.pendingEntries.get(id)?.length ?? 0,
|
|
4467
|
+
messages: this.messageCounts.get(id) ?? 0
|
|
4468
|
+
};
|
|
4469
|
+
});
|
|
4425
4470
|
this.logger.info("flushAll called", {
|
|
4426
4471
|
sessions: sessionIds.length,
|
|
4427
4472
|
pending: pendingCounts
|
|
@@ -4437,8 +4482,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4437
4482
|
return;
|
|
4438
4483
|
}
|
|
4439
4484
|
this.logger.info("Session registered", {
|
|
4440
|
-
|
|
4441
|
-
|
|
4485
|
+
taskId: context.taskId,
|
|
4486
|
+
runId: context.runId
|
|
4442
4487
|
});
|
|
4443
4488
|
this.sessions.set(sessionId, { context });
|
|
4444
4489
|
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
@@ -4472,7 +4517,11 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4472
4517
|
const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
|
|
4473
4518
|
this.messageCounts.set(sessionId, count);
|
|
4474
4519
|
if (count % 10 === 1) {
|
|
4475
|
-
this.logger.info("Messages received", {
|
|
4520
|
+
this.logger.info("Messages received", {
|
|
4521
|
+
count,
|
|
4522
|
+
taskId: session.context.taskId,
|
|
4523
|
+
runId: session.context.runId
|
|
4524
|
+
});
|
|
4476
4525
|
}
|
|
4477
4526
|
try {
|
|
4478
4527
|
const message = JSON.parse(line);
|
|
@@ -4489,6 +4538,10 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4489
4538
|
return;
|
|
4490
4539
|
}
|
|
4491
4540
|
this.emitCoalescedMessage(sessionId, session);
|
|
4541
|
+
const nonChunkAgentText = this.extractAgentMessageText(message);
|
|
4542
|
+
if (nonChunkAgentText) {
|
|
4543
|
+
session.lastAgentMessage = nonChunkAgentText;
|
|
4544
|
+
}
|
|
4492
4545
|
const entry = {
|
|
4493
4546
|
type: "notification",
|
|
4494
4547
|
timestamp,
|
|
@@ -4503,7 +4556,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4503
4556
|
}
|
|
4504
4557
|
} catch {
|
|
4505
4558
|
this.logger.warn("Failed to parse raw line for persistence", {
|
|
4506
|
-
|
|
4559
|
+
taskId: session.context.taskId,
|
|
4560
|
+
runId: session.context.runId,
|
|
4507
4561
|
lineLength: line.length
|
|
4508
4562
|
});
|
|
4509
4563
|
}
|
|
@@ -4518,7 +4572,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4518
4572
|
const pending = this.pendingEntries.get(sessionId);
|
|
4519
4573
|
if (!this.posthogAPI || !pending?.length) {
|
|
4520
4574
|
this.logger.info("flush: nothing to persist", {
|
|
4521
|
-
|
|
4575
|
+
taskId: session.context.taskId,
|
|
4576
|
+
runId: session.context.runId,
|
|
4522
4577
|
hasPosthogAPI: !!this.posthogAPI,
|
|
4523
4578
|
pendingCount: pending?.length ?? 0
|
|
4524
4579
|
});
|
|
@@ -4539,7 +4594,8 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4539
4594
|
);
|
|
4540
4595
|
this.retryCounts.set(sessionId, 0);
|
|
4541
4596
|
this.logger.info("Flushed session logs", {
|
|
4542
|
-
|
|
4597
|
+
taskId: session.context.taskId,
|
|
4598
|
+
runId: session.context.runId,
|
|
4543
4599
|
entryCount: pending.length
|
|
4544
4600
|
});
|
|
4545
4601
|
} catch (error) {
|
|
@@ -4548,7 +4604,11 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4548
4604
|
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
4549
4605
|
this.logger.error(
|
|
4550
4606
|
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
4551
|
-
{
|
|
4607
|
+
{
|
|
4608
|
+
taskId: session.context.taskId,
|
|
4609
|
+
runId: session.context.runId,
|
|
4610
|
+
error
|
|
4611
|
+
}
|
|
4552
4612
|
);
|
|
4553
4613
|
this.retryCounts.set(sessionId, 0);
|
|
4554
4614
|
} else {
|
|
@@ -4581,6 +4641,7 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4581
4641
|
if (!session.chunkBuffer) return;
|
|
4582
4642
|
const { text: text2, firstTimestamp } = session.chunkBuffer;
|
|
4583
4643
|
session.chunkBuffer = void 0;
|
|
4644
|
+
session.lastAgentMessage = text2;
|
|
4584
4645
|
const entry = {
|
|
4585
4646
|
type: "notification",
|
|
4586
4647
|
timestamp: firstTimestamp,
|
|
@@ -4603,6 +4664,29 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4603
4664
|
this.scheduleFlush(sessionId);
|
|
4604
4665
|
}
|
|
4605
4666
|
}
|
|
4667
|
+
getLastAgentMessage(sessionId) {
|
|
4668
|
+
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
4669
|
+
}
|
|
4670
|
+
extractAgentMessageText(message) {
|
|
4671
|
+
if (message.method !== "session/update") {
|
|
4672
|
+
return null;
|
|
4673
|
+
}
|
|
4674
|
+
const params = message.params;
|
|
4675
|
+
const update = params?.update;
|
|
4676
|
+
if (update?.sessionUpdate !== "agent_message") {
|
|
4677
|
+
return null;
|
|
4678
|
+
}
|
|
4679
|
+
const content = update.content;
|
|
4680
|
+
if (content?.type === "text" && typeof content.text === "string") {
|
|
4681
|
+
const trimmed2 = content.text.trim();
|
|
4682
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
4683
|
+
}
|
|
4684
|
+
if (typeof update.message === "string") {
|
|
4685
|
+
const trimmed2 = update.message.trim();
|
|
4686
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
4687
|
+
}
|
|
4688
|
+
return null;
|
|
4689
|
+
}
|
|
4606
4690
|
scheduleFlush(sessionId) {
|
|
4607
4691
|
const existing = this.flushTimeouts.get(sessionId);
|
|
4608
4692
|
if (existing) clearTimeout(existing);
|
|
@@ -4637,7 +4721,12 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
4637
4721
|
import_node_fs2.default.appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
4638
4722
|
`);
|
|
4639
4723
|
} catch (error) {
|
|
4640
|
-
this.logger.warn("Failed to write to local cache", {
|
|
4724
|
+
this.logger.warn("Failed to write to local cache", {
|
|
4725
|
+
taskId: session.context.taskId,
|
|
4726
|
+
runId: session.context.runId,
|
|
4727
|
+
logPath,
|
|
4728
|
+
error
|
|
4729
|
+
});
|
|
4641
4730
|
}
|
|
4642
4731
|
}
|
|
4643
4732
|
};
|
|
@@ -10270,6 +10359,8 @@ var AgentServer = class {
|
|
|
10270
10359
|
session = null;
|
|
10271
10360
|
app;
|
|
10272
10361
|
posthogAPI;
|
|
10362
|
+
questionRelayedToSlack = false;
|
|
10363
|
+
detectedPrUrl = null;
|
|
10273
10364
|
constructor(config) {
|
|
10274
10365
|
this.config = config;
|
|
10275
10366
|
this.logger = new Logger({ debug: true, prefix: "[AgentServer]" });
|
|
@@ -10482,11 +10573,21 @@ var AgentServer = class {
|
|
|
10482
10573
|
case "user_message": {
|
|
10483
10574
|
const content = params.content;
|
|
10484
10575
|
this.logger.info(
|
|
10485
|
-
`Processing user message: ${content.substring(0, 100)}...`
|
|
10576
|
+
`Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${content.substring(0, 100)}...`
|
|
10486
10577
|
);
|
|
10487
10578
|
const result = await this.session.clientConnection.prompt({
|
|
10488
10579
|
sessionId: this.session.acpSessionId,
|
|
10489
|
-
prompt: [{ type: "text", text: content }]
|
|
10580
|
+
prompt: [{ type: "text", text: content }],
|
|
10581
|
+
...this.detectedPrUrl && {
|
|
10582
|
+
_meta: {
|
|
10583
|
+
prContext: `IMPORTANT \u2014 OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.
|
|
10584
|
+
You already have an open pull request: ${this.detectedPrUrl}
|
|
10585
|
+
You MUST:
|
|
10586
|
+
1. Check out the existing PR branch with \`gh pr checkout ${this.detectedPrUrl}\`
|
|
10587
|
+
2. Make changes, commit, and push to that branch
|
|
10588
|
+
You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10490
10591
|
});
|
|
10491
10592
|
return { stopReason: result.stopReason };
|
|
10492
10593
|
}
|
|
@@ -10571,13 +10672,29 @@ var AgentServer = class {
|
|
|
10571
10672
|
protocolVersion: import_sdk4.PROTOCOL_VERSION,
|
|
10572
10673
|
clientCapabilities: {}
|
|
10573
10674
|
});
|
|
10675
|
+
let preTaskRun = null;
|
|
10676
|
+
try {
|
|
10677
|
+
preTaskRun = await this.posthogAPI.getTaskRun(
|
|
10678
|
+
payload.task_id,
|
|
10679
|
+
payload.run_id
|
|
10680
|
+
);
|
|
10681
|
+
} catch {
|
|
10682
|
+
this.logger.warn("Failed to fetch task run for session context", {
|
|
10683
|
+
taskId: payload.task_id,
|
|
10684
|
+
runId: payload.run_id
|
|
10685
|
+
});
|
|
10686
|
+
}
|
|
10687
|
+
const prUrl = typeof preTaskRun?.state?.slack_notified_pr_url === "string" ? (preTaskRun?.state).slack_notified_pr_url : null;
|
|
10688
|
+
if (prUrl) {
|
|
10689
|
+
this.detectedPrUrl = prUrl;
|
|
10690
|
+
}
|
|
10574
10691
|
const sessionResponse = await clientConnection.newSession({
|
|
10575
10692
|
cwd: this.config.repositoryPath,
|
|
10576
10693
|
mcpServers: [],
|
|
10577
10694
|
_meta: {
|
|
10578
10695
|
sessionId: payload.run_id,
|
|
10579
10696
|
taskRunId: payload.run_id,
|
|
10580
|
-
systemPrompt: { append: this.buildCloudSystemPrompt() }
|
|
10697
|
+
systemPrompt: { append: this.buildCloudSystemPrompt(prUrl) }
|
|
10581
10698
|
}
|
|
10582
10699
|
});
|
|
10583
10700
|
const acpSessionId = sessionResponse.sessionId;
|
|
@@ -10601,28 +10718,51 @@ var AgentServer = class {
|
|
|
10601
10718
|
}).catch(
|
|
10602
10719
|
(err) => this.logger.warn("Failed to set task run to in_progress", err)
|
|
10603
10720
|
);
|
|
10604
|
-
await this.sendInitialTaskMessage(payload);
|
|
10721
|
+
await this.sendInitialTaskMessage(payload, preTaskRun);
|
|
10605
10722
|
}
|
|
10606
|
-
async sendInitialTaskMessage(payload) {
|
|
10723
|
+
async sendInitialTaskMessage(payload, prefetchedRun) {
|
|
10607
10724
|
if (!this.session) return;
|
|
10608
10725
|
try {
|
|
10609
|
-
this.logger.info("Fetching task details", { taskId: payload.task_id });
|
|
10610
10726
|
const task = await this.posthogAPI.getTask(payload.task_id);
|
|
10611
|
-
|
|
10727
|
+
let taskRun = prefetchedRun ?? null;
|
|
10728
|
+
if (!taskRun) {
|
|
10729
|
+
try {
|
|
10730
|
+
taskRun = await this.posthogAPI.getTaskRun(
|
|
10731
|
+
payload.task_id,
|
|
10732
|
+
payload.run_id
|
|
10733
|
+
);
|
|
10734
|
+
} catch (error) {
|
|
10735
|
+
this.logger.warn(
|
|
10736
|
+
"Failed to fetch task run for initial prompt override",
|
|
10737
|
+
{
|
|
10738
|
+
taskId: payload.task_id,
|
|
10739
|
+
runId: payload.run_id,
|
|
10740
|
+
error
|
|
10741
|
+
}
|
|
10742
|
+
);
|
|
10743
|
+
}
|
|
10744
|
+
}
|
|
10745
|
+
const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
|
|
10746
|
+
const initialPrompt = initialPromptOverride ?? task.description;
|
|
10747
|
+
if (!initialPrompt) {
|
|
10612
10748
|
this.logger.warn("Task has no description, skipping initial message");
|
|
10613
10749
|
return;
|
|
10614
10750
|
}
|
|
10615
10751
|
this.logger.info("Sending initial task message", {
|
|
10616
10752
|
taskId: payload.task_id,
|
|
10617
|
-
descriptionLength:
|
|
10753
|
+
descriptionLength: initialPrompt.length,
|
|
10754
|
+
usedInitialPromptOverride: !!initialPromptOverride
|
|
10618
10755
|
});
|
|
10619
10756
|
const result = await this.session.clientConnection.prompt({
|
|
10620
10757
|
sessionId: this.session.acpSessionId,
|
|
10621
|
-
prompt: [{ type: "text", text:
|
|
10758
|
+
prompt: [{ type: "text", text: initialPrompt }]
|
|
10622
10759
|
});
|
|
10623
10760
|
this.logger.info("Initial task message completed", {
|
|
10624
10761
|
stopReason: result.stopReason
|
|
10625
10762
|
});
|
|
10763
|
+
if (result.stopReason === "end_turn") {
|
|
10764
|
+
await this.relayAgentResponse(payload);
|
|
10765
|
+
}
|
|
10626
10766
|
} catch (error) {
|
|
10627
10767
|
this.logger.error("Failed to send initial task message", error);
|
|
10628
10768
|
if (this.session) {
|
|
@@ -10631,7 +10771,33 @@ var AgentServer = class {
|
|
|
10631
10771
|
await this.signalTaskComplete(payload, "error");
|
|
10632
10772
|
}
|
|
10633
10773
|
}
|
|
10634
|
-
|
|
10774
|
+
getInitialPromptOverride(taskRun) {
|
|
10775
|
+
const state = taskRun.state;
|
|
10776
|
+
const override = state?.initial_prompt_override;
|
|
10777
|
+
if (typeof override !== "string") {
|
|
10778
|
+
return null;
|
|
10779
|
+
}
|
|
10780
|
+
const trimmed2 = override.trim();
|
|
10781
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
10782
|
+
}
|
|
10783
|
+
buildCloudSystemPrompt(prUrl) {
|
|
10784
|
+
if (prUrl) {
|
|
10785
|
+
return `
|
|
10786
|
+
# Cloud Task Execution
|
|
10787
|
+
|
|
10788
|
+
This task already has an open pull request: ${prUrl}
|
|
10789
|
+
|
|
10790
|
+
After completing the requested changes:
|
|
10791
|
+
1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`
|
|
10792
|
+
2. Stage and commit all changes with a clear commit message
|
|
10793
|
+
3. Push to the existing PR branch
|
|
10794
|
+
|
|
10795
|
+
Important:
|
|
10796
|
+
- Do NOT create a new branch or a new pull request.
|
|
10797
|
+
- Do NOT add "Co-Authored-By" trailers to commit messages.
|
|
10798
|
+
- Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
|
|
10799
|
+
`;
|
|
10800
|
+
}
|
|
10635
10801
|
return `
|
|
10636
10802
|
# Cloud Task Execution
|
|
10637
10803
|
|
|
@@ -10701,19 +10867,34 @@ Important:
|
|
|
10701
10867
|
}
|
|
10702
10868
|
createCloudClient(payload) {
|
|
10703
10869
|
const mode = this.getEffectiveMode(payload);
|
|
10870
|
+
const interactionOrigin = process.env.TWIG_INTERACTION_ORIGIN;
|
|
10704
10871
|
return {
|
|
10705
10872
|
requestPermission: async (params) => {
|
|
10706
10873
|
this.logger.debug("Permission request", {
|
|
10707
10874
|
mode,
|
|
10875
|
+
interactionOrigin,
|
|
10708
10876
|
options: params.options
|
|
10709
10877
|
});
|
|
10710
10878
|
const allowOption = params.options.find(
|
|
10711
10879
|
(o) => o.kind === "allow_once" || o.kind === "allow_always"
|
|
10712
10880
|
);
|
|
10881
|
+
const selectedOptionId = allowOption?.optionId ?? params.options[0].optionId;
|
|
10882
|
+
if (interactionOrigin === "slack") {
|
|
10883
|
+
const twigToolKind = params.toolCall?._meta?.twigToolKind;
|
|
10884
|
+
if (twigToolKind === "question") {
|
|
10885
|
+
this.relaySlackQuestion(payload, params.toolCall?._meta);
|
|
10886
|
+
return {
|
|
10887
|
+
outcome: { outcome: "cancelled" },
|
|
10888
|
+
_meta: {
|
|
10889
|
+
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."
|
|
10890
|
+
}
|
|
10891
|
+
};
|
|
10892
|
+
}
|
|
10893
|
+
}
|
|
10713
10894
|
return {
|
|
10714
10895
|
outcome: {
|
|
10715
10896
|
outcome: "selected",
|
|
10716
|
-
optionId:
|
|
10897
|
+
optionId: selectedOptionId
|
|
10717
10898
|
}
|
|
10718
10899
|
};
|
|
10719
10900
|
},
|
|
@@ -10732,6 +10913,97 @@ Important:
|
|
|
10732
10913
|
}
|
|
10733
10914
|
};
|
|
10734
10915
|
}
|
|
10916
|
+
async relayAgentResponse(payload) {
|
|
10917
|
+
if (!this.session) {
|
|
10918
|
+
return;
|
|
10919
|
+
}
|
|
10920
|
+
if (this.questionRelayedToSlack) {
|
|
10921
|
+
this.questionRelayedToSlack = false;
|
|
10922
|
+
return;
|
|
10923
|
+
}
|
|
10924
|
+
try {
|
|
10925
|
+
await this.session.logWriter.flush(payload.run_id);
|
|
10926
|
+
} catch (error) {
|
|
10927
|
+
this.logger.warn("Failed to flush logs before Slack relay", {
|
|
10928
|
+
taskId: payload.task_id,
|
|
10929
|
+
runId: payload.run_id,
|
|
10930
|
+
error
|
|
10931
|
+
});
|
|
10932
|
+
}
|
|
10933
|
+
const message = this.session.logWriter.getLastAgentMessage(payload.run_id);
|
|
10934
|
+
if (!message) {
|
|
10935
|
+
this.logger.warn("No agent message found for Slack relay", {
|
|
10936
|
+
taskId: payload.task_id,
|
|
10937
|
+
runId: payload.run_id,
|
|
10938
|
+
sessionRegistered: this.session.logWriter.isRegistered(payload.run_id)
|
|
10939
|
+
});
|
|
10940
|
+
return;
|
|
10941
|
+
}
|
|
10942
|
+
try {
|
|
10943
|
+
await this.posthogAPI.relayMessage(
|
|
10944
|
+
payload.task_id,
|
|
10945
|
+
payload.run_id,
|
|
10946
|
+
message
|
|
10947
|
+
);
|
|
10948
|
+
} catch (error) {
|
|
10949
|
+
this.logger.warn("Failed to relay initial agent response to Slack", {
|
|
10950
|
+
taskId: payload.task_id,
|
|
10951
|
+
runId: payload.run_id,
|
|
10952
|
+
error
|
|
10953
|
+
});
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
10956
|
+
relaySlackQuestion(payload, toolMeta2) {
|
|
10957
|
+
const firstQuestion = this.getFirstQuestionMeta(toolMeta2);
|
|
10958
|
+
if (!this.isQuestionMeta(firstQuestion)) {
|
|
10959
|
+
return;
|
|
10960
|
+
}
|
|
10961
|
+
let message = `*${firstQuestion.question}*
|
|
10962
|
+
|
|
10963
|
+
`;
|
|
10964
|
+
if (firstQuestion.options?.length) {
|
|
10965
|
+
firstQuestion.options.forEach(
|
|
10966
|
+
(opt, i) => {
|
|
10967
|
+
message += `${i + 1}. *${opt.label}*`;
|
|
10968
|
+
if (opt.description) message += ` \u2014 ${opt.description}`;
|
|
10969
|
+
message += "\n";
|
|
10970
|
+
}
|
|
10971
|
+
);
|
|
10972
|
+
}
|
|
10973
|
+
message += "\nReply in this thread with your choice.";
|
|
10974
|
+
this.questionRelayedToSlack = true;
|
|
10975
|
+
this.posthogAPI.relayMessage(payload.task_id, payload.run_id, message).catch(
|
|
10976
|
+
(err) => this.logger.warn("Failed to relay question to Slack", { err })
|
|
10977
|
+
);
|
|
10978
|
+
}
|
|
10979
|
+
getFirstQuestionMeta(toolMeta2) {
|
|
10980
|
+
if (!toolMeta2) {
|
|
10981
|
+
return null;
|
|
10982
|
+
}
|
|
10983
|
+
const questionsValue = toolMeta2.questions;
|
|
10984
|
+
if (!Array.isArray(questionsValue) || questionsValue.length === 0) {
|
|
10985
|
+
return null;
|
|
10986
|
+
}
|
|
10987
|
+
return questionsValue[0];
|
|
10988
|
+
}
|
|
10989
|
+
isQuestionMeta(value) {
|
|
10990
|
+
if (!value || typeof value !== "object") {
|
|
10991
|
+
return false;
|
|
10992
|
+
}
|
|
10993
|
+
const candidate = value;
|
|
10994
|
+
if (typeof candidate.question !== "string") {
|
|
10995
|
+
return false;
|
|
10996
|
+
}
|
|
10997
|
+
if (candidate.options === void 0) {
|
|
10998
|
+
return true;
|
|
10999
|
+
}
|
|
11000
|
+
if (!Array.isArray(candidate.options)) {
|
|
11001
|
+
return false;
|
|
11002
|
+
}
|
|
11003
|
+
return candidate.options.every(
|
|
11004
|
+
(option) => !!option && typeof option === "object" && typeof option.label === "string"
|
|
11005
|
+
);
|
|
11006
|
+
}
|
|
10735
11007
|
detectAndAttachPrUrl(payload, update) {
|
|
10736
11008
|
try {
|
|
10737
11009
|
const meta = update?._meta?.claudeCode;
|
|
@@ -10762,6 +11034,7 @@ Important:
|
|
|
10762
11034
|
);
|
|
10763
11035
|
if (!prUrlMatch) return;
|
|
10764
11036
|
const prUrl = prUrlMatch[0];
|
|
11037
|
+
this.detectedPrUrl = prUrl;
|
|
10765
11038
|
this.logger.info("Detected PR URL in bash output", {
|
|
10766
11039
|
runId: payload.run_id,
|
|
10767
11040
|
prUrl
|