@posthog/agent 2.3.387 → 2.3.398
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/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/mcp/tool-metadata.d.ts +24 -0
- package/dist/adapters/claude/mcp/tool-metadata.js +165 -0
- package/dist/adapters/claude/mcp/tool-metadata.js.map +1 -0
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.js +120 -3
- package/dist/agent.js.map +1 -1
- package/dist/handoff-checkpoint.d.ts +5 -1
- package/dist/handoff-checkpoint.js +22 -17
- package/dist/handoff-checkpoint.js.map +1 -1
- package/dist/index.d.ts +7 -9
- package/dist/index.js.map +1 -1
- package/dist/posthog-api.d.ts +1 -0
- package/dist/posthog-api.js +12 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/resume.d.ts +1 -7
- package/dist/resume.js +251 -6513
- package/dist/resume.js.map +1 -1
- package/dist/server/agent-server.d.ts +2 -1
- package/dist/server/agent-server.js +1305 -1181
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +1303 -1179
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +5 -1
- package/src/adapters/claude/claude-agent.ts +5 -0
- package/src/adapters/claude/mcp/tool-metadata.test.ts +93 -0
- package/src/adapters/claude/mcp/tool-metadata.ts +33 -0
- package/src/adapters/claude/permissions/permission-handlers.test.ts +165 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +105 -0
- package/src/adapters/claude/session/instructions.ts +9 -1
- package/src/adapters/claude/types.ts +2 -0
- package/src/handoff-checkpoint.ts +25 -19
- package/src/posthog-api.ts +8 -0
- package/src/resume.ts +20 -11
- package/src/sagas/resume-saga.test.ts +7 -47
- package/src/sagas/resume-saga.ts +10 -64
- package/src/server/agent-server.ts +119 -69
|
@@ -6,7 +6,6 @@ import { ResumeSaga } from "./resume-saga";
|
|
|
6
6
|
import {
|
|
7
7
|
createAgentChunk,
|
|
8
8
|
createAgentMessage,
|
|
9
|
-
createArchiveBuffer,
|
|
10
9
|
createMockApiClient,
|
|
11
10
|
createMockLogger,
|
|
12
11
|
createNotification,
|
|
@@ -52,7 +51,6 @@ describe("ResumeSaga", () => {
|
|
|
52
51
|
if (result.success) {
|
|
53
52
|
expect(result.data.conversation).toHaveLength(0);
|
|
54
53
|
expect(result.data.latestSnapshot).toBeNull();
|
|
55
|
-
expect(result.data.snapshotApplied).toBe(false);
|
|
56
54
|
expect(result.data.logEntryCount).toBe(0);
|
|
57
55
|
}
|
|
58
56
|
});
|
|
@@ -468,14 +466,10 @@ describe("ResumeSaga", () => {
|
|
|
468
466
|
});
|
|
469
467
|
});
|
|
470
468
|
|
|
471
|
-
describe("snapshot
|
|
472
|
-
it("
|
|
469
|
+
describe("snapshot metadata", () => {
|
|
470
|
+
it("returns latest snapshot metadata when archive URL present", async () => {
|
|
473
471
|
const baseCommit = await repo.git(["rev-parse", "HEAD"]);
|
|
474
472
|
|
|
475
|
-
const archive = await createArchiveBuffer([
|
|
476
|
-
{ path: "restored.ts", content: "restored content" },
|
|
477
|
-
]);
|
|
478
|
-
|
|
479
473
|
(mockApiClient.getTaskRun as ReturnType<typeof vi.fn>).mockResolvedValue(
|
|
480
474
|
createTaskRun(),
|
|
481
475
|
);
|
|
@@ -490,9 +484,6 @@ describe("ResumeSaga", () => {
|
|
|
490
484
|
timestamp: new Date().toISOString(),
|
|
491
485
|
}),
|
|
492
486
|
]);
|
|
493
|
-
(
|
|
494
|
-
mockApiClient.downloadArtifact as ReturnType<typeof vi.fn>
|
|
495
|
-
).mockResolvedValue(archive);
|
|
496
487
|
|
|
497
488
|
const saga = new ResumeSaga(mockLogger);
|
|
498
489
|
const result = await saga.run({
|
|
@@ -505,13 +496,13 @@ describe("ResumeSaga", () => {
|
|
|
505
496
|
expect(result.success).toBe(true);
|
|
506
497
|
if (!result.success) return;
|
|
507
498
|
|
|
508
|
-
expect(result.data.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
499
|
+
expect(result.data.latestSnapshot?.treeHash).toBe("hash-1");
|
|
500
|
+
expect(result.data.latestSnapshot?.archiveUrl).toBe(
|
|
501
|
+
"gs://bucket/hash-1.tar.gz",
|
|
502
|
+
);
|
|
512
503
|
});
|
|
513
504
|
|
|
514
|
-
it("
|
|
505
|
+
it("returns snapshot metadata even when no archive URL", async () => {
|
|
515
506
|
(mockApiClient.getTaskRun as ReturnType<typeof vi.fn>).mockResolvedValue(
|
|
516
507
|
createTaskRun(),
|
|
517
508
|
);
|
|
@@ -533,40 +524,9 @@ describe("ResumeSaga", () => {
|
|
|
533
524
|
expect(result.success).toBe(true);
|
|
534
525
|
if (!result.success) return;
|
|
535
526
|
|
|
536
|
-
expect(result.data.snapshotApplied).toBe(false);
|
|
537
527
|
expect(result.data.latestSnapshot).not.toBeNull();
|
|
538
528
|
expect(result.data.conversation).toHaveLength(1);
|
|
539
529
|
});
|
|
540
|
-
|
|
541
|
-
it("continues without snapshot when apply fails", async () => {
|
|
542
|
-
(mockApiClient.getTaskRun as ReturnType<typeof vi.fn>).mockResolvedValue(
|
|
543
|
-
createTaskRun(),
|
|
544
|
-
);
|
|
545
|
-
(
|
|
546
|
-
mockApiClient.fetchTaskRunLogs as ReturnType<typeof vi.fn>
|
|
547
|
-
).mockResolvedValue([
|
|
548
|
-
createTreeSnapshotNotification("hash-1", "gs://bucket/hash-1.tar.gz"),
|
|
549
|
-
createUserMessage("hello"),
|
|
550
|
-
]);
|
|
551
|
-
(
|
|
552
|
-
mockApiClient.downloadArtifact as ReturnType<typeof vi.fn>
|
|
553
|
-
).mockRejectedValue(new Error("Download failed"));
|
|
554
|
-
|
|
555
|
-
const saga = new ResumeSaga(mockLogger);
|
|
556
|
-
const result = await saga.run({
|
|
557
|
-
taskId: "task-1",
|
|
558
|
-
runId: "run-1",
|
|
559
|
-
repositoryPath: repo.path,
|
|
560
|
-
apiClient: mockApiClient,
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
expect(result.success).toBe(true);
|
|
564
|
-
if (!result.success) return;
|
|
565
|
-
|
|
566
|
-
expect(result.data.snapshotApplied).toBe(false);
|
|
567
|
-
expect(result.data.conversation).toHaveLength(1);
|
|
568
|
-
expect(mockLogger.warn).toHaveBeenCalled();
|
|
569
|
-
});
|
|
570
530
|
});
|
|
571
531
|
|
|
572
532
|
describe("device info", () => {
|
package/src/sagas/resume-saga.ts
CHANGED
|
@@ -2,14 +2,13 @@ import type { ContentBlock } from "@agentclientprotocol/sdk";
|
|
|
2
2
|
import { Saga } from "@posthog/shared";
|
|
3
3
|
import { isNotification, POSTHOG_NOTIFICATIONS } from "../acp-extensions";
|
|
4
4
|
import type { PostHogAPIClient } from "../posthog-api";
|
|
5
|
-
import { TreeTracker } from "../tree-tracker";
|
|
6
5
|
import type {
|
|
7
6
|
DeviceInfo,
|
|
8
7
|
GitCheckpointEvent,
|
|
9
8
|
StoredNotification,
|
|
10
9
|
TreeSnapshotEvent,
|
|
11
10
|
} from "../types";
|
|
12
|
-
import { Logger } from "../utils/logger";
|
|
11
|
+
import type { Logger } from "../utils/logger";
|
|
13
12
|
|
|
14
13
|
export interface ConversationTurn {
|
|
15
14
|
role: "user" | "assistant";
|
|
@@ -36,7 +35,6 @@ export interface ResumeOutput {
|
|
|
36
35
|
conversation: ConversationTurn[];
|
|
37
36
|
latestSnapshot: TreeSnapshotEvent | null;
|
|
38
37
|
latestGitCheckpoint: GitCheckpointEvent | null;
|
|
39
|
-
snapshotApplied: boolean;
|
|
40
38
|
interrupted: boolean;
|
|
41
39
|
lastDevice?: DeviceInfo;
|
|
42
40
|
logEntryCount: number;
|
|
@@ -46,9 +44,7 @@ export class ResumeSaga extends Saga<ResumeInput, ResumeOutput> {
|
|
|
46
44
|
readonly sagaName = "ResumeSaga";
|
|
47
45
|
|
|
48
46
|
protected async execute(input: ResumeInput): Promise<ResumeOutput> {
|
|
49
|
-
const { taskId, runId,
|
|
50
|
-
const logger =
|
|
51
|
-
input.logger || new Logger({ debug: false, prefix: "[Resume]" });
|
|
47
|
+
const { taskId, runId, apiClient } = input;
|
|
52
48
|
|
|
53
49
|
// Step 1: Fetch task run (read-only)
|
|
54
50
|
const taskRun = await this.readOnlyStep("fetch_task_run", () =>
|
|
@@ -83,69 +79,21 @@ export class ResumeSaga extends Saga<ResumeInput, ResumeOutput> {
|
|
|
83
79
|
);
|
|
84
80
|
|
|
85
81
|
// Step 4: Apply snapshot if present (wrapped in step for consistent logging)
|
|
86
|
-
|
|
87
|
-
let snapshotApplied = false;
|
|
88
|
-
if (latestSnapshot?.archiveUrl && repositoryPath) {
|
|
82
|
+
if (latestSnapshot) {
|
|
89
83
|
this.log.info("Found tree snapshot", {
|
|
90
84
|
treeHash: latestSnapshot.treeHash,
|
|
91
|
-
hasArchiveUrl:
|
|
85
|
+
hasArchiveUrl: !!latestSnapshot.archiveUrl,
|
|
92
86
|
changes: latestSnapshot.changes?.length ?? 0,
|
|
93
|
-
interrupted: latestSnapshot.interrupted,
|
|
94
87
|
});
|
|
88
|
+
}
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
repositoryPath,
|
|
101
|
-
taskId,
|
|
102
|
-
runId,
|
|
103
|
-
apiClient,
|
|
104
|
-
logger: logger.child("TreeTracker"),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await treeTracker.applyTreeSnapshot(latestSnapshot);
|
|
109
|
-
treeTracker.setLastTreeHash(latestSnapshot.treeHash);
|
|
110
|
-
snapshotApplied = true;
|
|
111
|
-
this.log.info("Tree snapshot applied successfully", {
|
|
112
|
-
treeHash: latestSnapshot.treeHash,
|
|
113
|
-
});
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// Log but don't fail - continue with conversation rebuild
|
|
116
|
-
// ApplySnapshotSaga handles its own rollback internally
|
|
117
|
-
this.log.warn(
|
|
118
|
-
"Failed to apply tree snapshot, continuing without it",
|
|
119
|
-
{
|
|
120
|
-
error: error instanceof Error ? error.message : String(error),
|
|
121
|
-
treeHash: latestSnapshot.treeHash,
|
|
122
|
-
},
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
rollback: async () => {
|
|
127
|
-
// Inner ApplySnapshotSaga handles its own rollback
|
|
128
|
-
},
|
|
90
|
+
if (latestGitCheckpoint) {
|
|
91
|
+
this.log.info("Found git checkpoint", {
|
|
92
|
+
checkpointId: latestGitCheckpoint.checkpointId,
|
|
93
|
+
branch: latestGitCheckpoint.branch,
|
|
129
94
|
});
|
|
130
|
-
} else if (latestSnapshot?.archiveUrl && !repositoryPath) {
|
|
131
|
-
this.log.warn(
|
|
132
|
-
"Snapshot found but no repositoryPath configured - files cannot be restored",
|
|
133
|
-
{
|
|
134
|
-
treeHash: latestSnapshot.treeHash,
|
|
135
|
-
changes: latestSnapshot.changes?.length ?? 0,
|
|
136
|
-
},
|
|
137
|
-
);
|
|
138
|
-
} else if (latestSnapshot) {
|
|
139
|
-
this.log.warn(
|
|
140
|
-
"Snapshot found but has no archive URL - files cannot be restored",
|
|
141
|
-
{
|
|
142
|
-
treeHash: latestSnapshot.treeHash,
|
|
143
|
-
changes: latestSnapshot.changes?.length ?? 0,
|
|
144
|
-
},
|
|
145
|
-
);
|
|
146
95
|
}
|
|
147
96
|
|
|
148
|
-
// Step 5: Rebuild conversation (read-only, pure computation)
|
|
149
97
|
const conversation = await this.readOnlyStep("rebuild_conversation", () =>
|
|
150
98
|
Promise.resolve(this.rebuildConversation(entries)),
|
|
151
99
|
);
|
|
@@ -158,7 +106,7 @@ export class ResumeSaga extends Saga<ResumeInput, ResumeOutput> {
|
|
|
158
106
|
this.log.info("Resume state rebuilt", {
|
|
159
107
|
turns: conversation.length,
|
|
160
108
|
hasSnapshot: !!latestSnapshot,
|
|
161
|
-
|
|
109
|
+
hasGitCheckpoint: !!latestGitCheckpoint,
|
|
162
110
|
interrupted: latestSnapshot?.interrupted ?? false,
|
|
163
111
|
});
|
|
164
112
|
|
|
@@ -166,7 +114,6 @@ export class ResumeSaga extends Saga<ResumeInput, ResumeOutput> {
|
|
|
166
114
|
conversation,
|
|
167
115
|
latestSnapshot,
|
|
168
116
|
latestGitCheckpoint,
|
|
169
|
-
snapshotApplied,
|
|
170
117
|
interrupted: latestSnapshot?.interrupted ?? false,
|
|
171
118
|
lastDevice,
|
|
172
119
|
logEntryCount: entries.length,
|
|
@@ -178,7 +125,6 @@ export class ResumeSaga extends Saga<ResumeInput, ResumeOutput> {
|
|
|
178
125
|
conversation: [],
|
|
179
126
|
latestSnapshot: null,
|
|
180
127
|
latestGitCheckpoint: null,
|
|
181
|
-
snapshotApplied: false,
|
|
182
128
|
interrupted: false,
|
|
183
129
|
logEntryCount: 0,
|
|
184
130
|
};
|
|
@@ -478,50 +478,34 @@ export class AgentServer {
|
|
|
478
478
|
await this.autoInitializeSession();
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
-
private async
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
resumeRunId,
|
|
491
|
-
|
|
481
|
+
private async loadResumeState(
|
|
482
|
+
taskId: string,
|
|
483
|
+
resumeRunId: string,
|
|
484
|
+
currentRunId: string,
|
|
485
|
+
): Promise<void> {
|
|
486
|
+
this.logger.debug("Loading resume state", { resumeRunId, currentRunId });
|
|
487
|
+
try {
|
|
488
|
+
this.resumeState = await resumeFromLog({
|
|
489
|
+
taskId,
|
|
490
|
+
runId: resumeRunId,
|
|
491
|
+
repositoryPath: this.config.repositoryPath,
|
|
492
|
+
apiClient: this.posthogAPI,
|
|
493
|
+
logger: new Logger({ debug: true, prefix: "[Resume]" }),
|
|
492
494
|
});
|
|
493
|
-
|
|
494
|
-
this.resumeState
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
} catch (error) {
|
|
507
|
-
this.logger.debug("Failed to load resume state, starting fresh", {
|
|
508
|
-
error,
|
|
509
|
-
});
|
|
510
|
-
this.resumeState = null;
|
|
511
|
-
}
|
|
495
|
+
this.logger.debug("Resume state loaded", {
|
|
496
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
497
|
+
hasSnapshot: !!this.resumeState.latestSnapshot,
|
|
498
|
+
hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
|
|
499
|
+
gitCheckpointBranch:
|
|
500
|
+
this.resumeState.latestGitCheckpoint?.branch ?? null,
|
|
501
|
+
logEntries: this.resumeState.logEntryCount,
|
|
502
|
+
});
|
|
503
|
+
} catch (error) {
|
|
504
|
+
this.logger.debug("Failed to load resume state, starting fresh", {
|
|
505
|
+
error,
|
|
506
|
+
});
|
|
507
|
+
this.resumeState = null;
|
|
512
508
|
}
|
|
513
|
-
|
|
514
|
-
// Create a synthetic payload from config (no JWT needed for auto-init)
|
|
515
|
-
const payload: JwtPayload = {
|
|
516
|
-
task_id: taskId,
|
|
517
|
-
run_id: runId,
|
|
518
|
-
team_id: projectId,
|
|
519
|
-
user_id: 0, // System-initiated
|
|
520
|
-
distinct_id: "agent-server",
|
|
521
|
-
mode,
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
await this.initializeSession(payload, null);
|
|
525
509
|
}
|
|
526
510
|
|
|
527
511
|
async stop(): Promise<void> {
|
|
@@ -1057,33 +1041,14 @@ export class AgentServer {
|
|
|
1057
1041
|
}
|
|
1058
1042
|
}
|
|
1059
1043
|
|
|
1060
|
-
// Check for resume if not already loaded from env var in autoInitializeSession
|
|
1061
1044
|
if (!this.resumeState) {
|
|
1062
1045
|
const resumeRunId = this.getResumeRunId(taskRun);
|
|
1063
1046
|
if (resumeRunId) {
|
|
1064
|
-
this.
|
|
1047
|
+
await this.loadResumeState(
|
|
1048
|
+
payload.task_id,
|
|
1065
1049
|
resumeRunId,
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
try {
|
|
1069
|
-
this.resumeState = await resumeFromLog({
|
|
1070
|
-
taskId: payload.task_id,
|
|
1071
|
-
runId: resumeRunId,
|
|
1072
|
-
repositoryPath: this.config.repositoryPath,
|
|
1073
|
-
apiClient: this.posthogAPI,
|
|
1074
|
-
logger: new Logger({ debug: true, prefix: "[Resume]" }),
|
|
1075
|
-
});
|
|
1076
|
-
this.logger.debug("Resume state loaded (via TaskRun state)", {
|
|
1077
|
-
conversationTurns: this.resumeState.conversation.length,
|
|
1078
|
-
snapshotApplied: this.resumeState.snapshotApplied,
|
|
1079
|
-
logEntries: this.resumeState.logEntryCount,
|
|
1080
|
-
});
|
|
1081
|
-
} catch (error) {
|
|
1082
|
-
this.logger.debug("Failed to load resume state, starting fresh", {
|
|
1083
|
-
error,
|
|
1084
|
-
});
|
|
1085
|
-
this.resumeState = null;
|
|
1086
|
-
}
|
|
1050
|
+
payload.run_id,
|
|
1051
|
+
);
|
|
1087
1052
|
}
|
|
1088
1053
|
}
|
|
1089
1054
|
|
|
@@ -1163,11 +1128,70 @@ export class AgentServer {
|
|
|
1163
1128
|
this.resumeState.conversation,
|
|
1164
1129
|
);
|
|
1165
1130
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1131
|
+
let snapshotApplied = false;
|
|
1132
|
+
if (
|
|
1133
|
+
this.resumeState.latestSnapshot?.archiveUrl &&
|
|
1134
|
+
this.config.repositoryPath &&
|
|
1135
|
+
this.posthogAPI
|
|
1136
|
+
) {
|
|
1137
|
+
try {
|
|
1138
|
+
const treeTracker = new TreeTracker({
|
|
1139
|
+
repositoryPath: this.config.repositoryPath,
|
|
1140
|
+
taskId: payload.task_id,
|
|
1141
|
+
runId: payload.run_id,
|
|
1142
|
+
apiClient: this.posthogAPI,
|
|
1143
|
+
logger: this.logger.child("TreeTracker"),
|
|
1144
|
+
});
|
|
1145
|
+
await treeTracker.applyTreeSnapshot(this.resumeState.latestSnapshot);
|
|
1146
|
+
treeTracker.setLastTreeHash(this.resumeState.latestSnapshot.treeHash);
|
|
1147
|
+
snapshotApplied = true;
|
|
1148
|
+
this.logger.info("Tree snapshot applied", {
|
|
1149
|
+
treeHash: this.resumeState.latestSnapshot.treeHash,
|
|
1150
|
+
changes: this.resumeState.latestSnapshot.changes?.length ?? 0,
|
|
1151
|
+
hasArchiveUrl: !!this.resumeState.latestSnapshot.archiveUrl,
|
|
1152
|
+
});
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
this.logger.warn("Failed to apply tree snapshot", {
|
|
1155
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1156
|
+
treeHash: this.resumeState.latestSnapshot.treeHash,
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (
|
|
1162
|
+
this.resumeState.latestGitCheckpoint &&
|
|
1163
|
+
this.config.repositoryPath &&
|
|
1164
|
+
this.posthogAPI
|
|
1165
|
+
) {
|
|
1166
|
+
try {
|
|
1167
|
+
const checkpointTracker = new HandoffCheckpointTracker({
|
|
1168
|
+
repositoryPath: this.config.repositoryPath,
|
|
1169
|
+
taskId: payload.task_id,
|
|
1170
|
+
runId: payload.run_id,
|
|
1171
|
+
apiClient: this.posthogAPI,
|
|
1172
|
+
logger: this.logger.child("HandoffCheckpoint"),
|
|
1173
|
+
});
|
|
1174
|
+
const metrics = await checkpointTracker.applyFromHandoff(
|
|
1175
|
+
this.resumeState.latestGitCheckpoint,
|
|
1176
|
+
);
|
|
1177
|
+
this.logger.info("Git checkpoint applied", {
|
|
1178
|
+
branch: this.resumeState.latestGitCheckpoint.branch,
|
|
1179
|
+
head: this.resumeState.latestGitCheckpoint.head,
|
|
1180
|
+
packBytes: metrics.packBytes,
|
|
1181
|
+
indexBytes: metrics.indexBytes,
|
|
1182
|
+
totalBytes: metrics.totalBytes,
|
|
1183
|
+
});
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
this.logger.warn("Failed to apply git checkpoint", {
|
|
1186
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1187
|
+
branch: this.resumeState.latestGitCheckpoint.branch,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1168
1192
|
const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
|
|
1169
1193
|
|
|
1170
|
-
const sandboxContext =
|
|
1194
|
+
const sandboxContext = snapshotApplied
|
|
1171
1195
|
? `The workspace environment (all files, packages, and code changes) has been fully restored from where you left off.`
|
|
1172
1196
|
: `The workspace files from the previous session were not restored (the file snapshot may have expired), so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
|
|
1173
1197
|
|
|
@@ -1206,7 +1230,10 @@ export class AgentServer {
|
|
|
1206
1230
|
conversationTurns: this.resumeState.conversation.length,
|
|
1207
1231
|
promptLength: promptBlocksToText(resumePromptBlocks).length,
|
|
1208
1232
|
hasPendingUserMessage: !!pendingUserPrompt?.length,
|
|
1209
|
-
snapshotApplied
|
|
1233
|
+
snapshotApplied,
|
|
1234
|
+
hasGitCheckpoint: !!this.resumeState.latestGitCheckpoint,
|
|
1235
|
+
gitCheckpointBranch:
|
|
1236
|
+
this.resumeState.latestGitCheckpoint?.branch ?? null,
|
|
1210
1237
|
});
|
|
1211
1238
|
|
|
1212
1239
|
// Clear resume state so it's not reused
|
|
@@ -1434,6 +1461,29 @@ export class AgentServer {
|
|
|
1434
1461
|
return normalizedName.length > 0 ? normalizedName : "attachment";
|
|
1435
1462
|
}
|
|
1436
1463
|
|
|
1464
|
+
private async autoInitializeSession(): Promise<void> {
|
|
1465
|
+
const { taskId, runId, mode, projectId } = this.config;
|
|
1466
|
+
|
|
1467
|
+
this.logger.debug("Auto-initializing session", { taskId, runId, mode });
|
|
1468
|
+
|
|
1469
|
+
const resumeRunId = process.env.POSTHOG_RESUME_RUN_ID;
|
|
1470
|
+
if (resumeRunId) {
|
|
1471
|
+
await this.loadResumeState(taskId, resumeRunId, runId);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Create a synthetic payload from config (no JWT needed for auto-init)
|
|
1475
|
+
const payload: JwtPayload = {
|
|
1476
|
+
task_id: taskId,
|
|
1477
|
+
run_id: runId,
|
|
1478
|
+
team_id: projectId,
|
|
1479
|
+
user_id: 0, // System-initiated
|
|
1480
|
+
distinct_id: "agent-server",
|
|
1481
|
+
mode,
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
await this.initializeSession(payload, null);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1437
1487
|
private getResumeRunId(taskRun: TaskRun | null): string | null {
|
|
1438
1488
|
// Env var takes precedence (set by backend infra)
|
|
1439
1489
|
const envRunId = process.env.POSTHOG_RESUME_RUN_ID;
|