@posthog/agent 2.3.126 → 2.3.137
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 +57 -29
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +1 -0
- package/dist/server/agent-server.js +71 -37
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +71 -37
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/claude-agent.ts +72 -38
- package/src/adapters/claude/types.ts +2 -0
- package/src/server/agent-server.ts +15 -8
- package/src/server/question-relay.test.ts +65 -0
package/package.json
CHANGED
|
@@ -876,58 +876,92 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
876
876
|
{ sessionId, taskId, taskRunId: meta?.taskRunId },
|
|
877
877
|
);
|
|
878
878
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
`Session ${isResume ? (forkSession ? "fork" : "resumption") : "initialization"} timed out for sessionId=${sessionId}`,
|
|
879
|
+
if (isResume) {
|
|
880
|
+
// Resume must block on initialization to validate the session is still alive.
|
|
881
|
+
// For stale sessions this throws (e.g. "No conversation found").
|
|
882
|
+
try {
|
|
883
|
+
const result = await withTimeout(
|
|
884
|
+
q.initializationResult(),
|
|
885
|
+
SESSION_VALIDATION_TIMEOUT_MS,
|
|
887
886
|
);
|
|
887
|
+
if (result.result === "timeout") {
|
|
888
|
+
throw new Error(
|
|
889
|
+
`Session ${forkSession ? "fork" : "resumption"} timed out for sessionId=${sessionId}`,
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
} catch (err) {
|
|
893
|
+
settingsManager.dispose();
|
|
894
|
+
if (
|
|
895
|
+
err instanceof Error &&
|
|
896
|
+
err.message === "Query closed before response received"
|
|
897
|
+
) {
|
|
898
|
+
throw RequestError.resourceNotFound(sessionId);
|
|
899
|
+
}
|
|
900
|
+
this.logger.error(
|
|
901
|
+
forkSession ? "Session fork failed" : "Session resumption failed",
|
|
902
|
+
{
|
|
903
|
+
sessionId,
|
|
904
|
+
taskId,
|
|
905
|
+
taskRunId: meta?.taskRunId,
|
|
906
|
+
error: err instanceof Error ? err.message : String(err),
|
|
907
|
+
},
|
|
908
|
+
);
|
|
909
|
+
throw err;
|
|
888
910
|
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
this.
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Kick off SDK initialization for new sessions so it runs concurrently
|
|
914
|
+
// with the model config fetch below (the gateway REST call is independent).
|
|
915
|
+
const initPromise = !isResume
|
|
916
|
+
? withTimeout(q.initializationResult(), SESSION_VALIDATION_TIMEOUT_MS)
|
|
917
|
+
: undefined;
|
|
918
|
+
|
|
919
|
+
const [modelOptions] = await Promise.all([
|
|
920
|
+
this.getModelConfigOptions(
|
|
921
|
+
settingsManager.getSettings().model || meta?.model || undefined,
|
|
922
|
+
),
|
|
923
|
+
...(meta?.taskRunId
|
|
924
|
+
? [
|
|
925
|
+
this.client.extNotification("_posthog/sdk_session", {
|
|
926
|
+
taskRunId: meta.taskRunId,
|
|
927
|
+
sessionId,
|
|
928
|
+
adapter: "claude",
|
|
929
|
+
}),
|
|
930
|
+
]
|
|
931
|
+
: []),
|
|
932
|
+
]);
|
|
933
|
+
|
|
934
|
+
if (initPromise) {
|
|
935
|
+
try {
|
|
936
|
+
const initResult = await initPromise;
|
|
937
|
+
if (initResult.result === "timeout") {
|
|
938
|
+
settingsManager.dispose();
|
|
939
|
+
throw new Error(
|
|
940
|
+
`Session initialization timed out for sessionId=${sessionId}`,
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
} catch (err) {
|
|
944
|
+
settingsManager.dispose();
|
|
945
|
+
this.logger.error("Session initialization failed", {
|
|
905
946
|
sessionId,
|
|
906
947
|
taskId,
|
|
907
948
|
taskRunId: meta?.taskRunId,
|
|
908
949
|
error: err instanceof Error ? err.message : String(err),
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
if (meta?.taskRunId) {
|
|
915
|
-
await this.client.extNotification("_posthog/sdk_session", {
|
|
916
|
-
taskRunId: meta.taskRunId,
|
|
917
|
-
sessionId,
|
|
918
|
-
adapter: "claude",
|
|
919
|
-
});
|
|
950
|
+
});
|
|
951
|
+
throw err;
|
|
952
|
+
}
|
|
920
953
|
}
|
|
921
954
|
|
|
922
|
-
// Resolve model: settings model takes priority, then gateway
|
|
923
955
|
const settingsModel = settingsManager.getSettings().model;
|
|
924
|
-
const
|
|
925
|
-
const resolvedModelId =
|
|
956
|
+
const metaModel = meta?.model;
|
|
957
|
+
const resolvedModelId =
|
|
958
|
+
settingsModel || metaModel || modelOptions.currentModelId;
|
|
926
959
|
session.modelId = resolvedModelId;
|
|
927
960
|
session.lastContextWindowSize =
|
|
928
961
|
this.getContextWindowForModel(resolvedModelId);
|
|
929
962
|
|
|
930
963
|
const resolvedSdkModel = toSdkModelId(resolvedModelId);
|
|
964
|
+
|
|
931
965
|
if (!isResume && resolvedSdkModel !== DEFAULT_MODEL) {
|
|
932
966
|
await this.session.query.setModel(resolvedSdkModel);
|
|
933
967
|
}
|
|
@@ -108,6 +108,8 @@ export type NewSessionMeta = {
|
|
|
108
108
|
persistence?: { taskId?: string; runId?: string; logUrl?: string };
|
|
109
109
|
additionalRoots?: string[];
|
|
110
110
|
allowedDomains?: string[];
|
|
111
|
+
/** Model ID to use for this session (e.g. "claude-sonnet-4-6") */
|
|
112
|
+
model?: string;
|
|
111
113
|
claudeCode?: {
|
|
112
114
|
options?: Options;
|
|
113
115
|
};
|
|
@@ -169,6 +169,12 @@ export class AgentServer {
|
|
|
169
169
|
private initializationPromise: Promise<void> | null = null;
|
|
170
170
|
private pendingEvents: Record<string, unknown>[] = [];
|
|
171
171
|
|
|
172
|
+
private detachSseController(controller: SseController): void {
|
|
173
|
+
if (this.session?.sseController === controller) {
|
|
174
|
+
this.session.sseController = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
private emitConsoleLog = (
|
|
173
179
|
level: LogLevel,
|
|
174
180
|
_scope: string,
|
|
@@ -250,18 +256,15 @@ export class AgentServer {
|
|
|
250
256
|
controller.enqueue(
|
|
251
257
|
new TextEncoder().encode(`data: ${JSON.stringify(data)}\n\n`),
|
|
252
258
|
);
|
|
253
|
-
} catch
|
|
254
|
-
this.
|
|
255
|
-
"SSE send failed (stream may be closed)",
|
|
256
|
-
error,
|
|
257
|
-
);
|
|
259
|
+
} catch {
|
|
260
|
+
this.detachSseController(sseController);
|
|
258
261
|
}
|
|
259
262
|
},
|
|
260
263
|
close: () => {
|
|
261
264
|
try {
|
|
262
265
|
controller.close();
|
|
263
|
-
} catch
|
|
264
|
-
this.
|
|
266
|
+
} catch {
|
|
267
|
+
this.detachSseController(sseController);
|
|
265
268
|
}
|
|
266
269
|
},
|
|
267
270
|
};
|
|
@@ -1577,6 +1580,10 @@ Important:
|
|
|
1577
1580
|
}
|
|
1578
1581
|
|
|
1579
1582
|
private sendSseEvent(controller: SseController, data: unknown): void {
|
|
1580
|
-
|
|
1583
|
+
try {
|
|
1584
|
+
controller.send(data);
|
|
1585
|
+
} catch {
|
|
1586
|
+
this.detachSseController(controller);
|
|
1587
|
+
}
|
|
1581
1588
|
}
|
|
1582
1589
|
}
|
|
@@ -235,6 +235,71 @@ describe("Question relay", () => {
|
|
|
235
235
|
|
|
236
236
|
expect(result.outcome.outcome).toBe("selected");
|
|
237
237
|
});
|
|
238
|
+
|
|
239
|
+
it("keeps auto-approving permissions after SSE send failures", async () => {
|
|
240
|
+
const appendRawLine = vi.fn();
|
|
241
|
+
const brokenSseController = {
|
|
242
|
+
send: vi.fn(() => {
|
|
243
|
+
throw new Error("stream closed");
|
|
244
|
+
}),
|
|
245
|
+
close: vi.fn(),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const cloudPermissionServer = server as TestableAgentServer & {
|
|
249
|
+
emitConsoleLog: (
|
|
250
|
+
level: "debug" | "info" | "warn" | "error",
|
|
251
|
+
scope: string,
|
|
252
|
+
message: string,
|
|
253
|
+
data?: unknown,
|
|
254
|
+
) => void;
|
|
255
|
+
logger: { debug: (message: string, data?: unknown) => void };
|
|
256
|
+
session: {
|
|
257
|
+
payload: typeof TEST_PAYLOAD;
|
|
258
|
+
sseController: typeof brokenSseController | null;
|
|
259
|
+
logWriter: {
|
|
260
|
+
appendRawLine: (runId: string, line: string) => void;
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
cloudPermissionServer.session = {
|
|
266
|
+
payload: TEST_PAYLOAD,
|
|
267
|
+
sseController: brokenSseController,
|
|
268
|
+
logWriter: {
|
|
269
|
+
appendRawLine,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
cloudPermissionServer.logger = {
|
|
273
|
+
debug: (message: string, data?: unknown) => {
|
|
274
|
+
cloudPermissionServer.emitConsoleLog(
|
|
275
|
+
"debug",
|
|
276
|
+
"agent",
|
|
277
|
+
message,
|
|
278
|
+
data,
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const client = cloudPermissionServer.createCloudClient(TEST_PAYLOAD);
|
|
284
|
+
|
|
285
|
+
const firstResult = await client.requestPermission({
|
|
286
|
+
options: ALLOW_OPTIONS,
|
|
287
|
+
toolCall: { _meta: { codeToolKind: "bash" } },
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(firstResult.outcome.outcome).toBe("selected");
|
|
291
|
+
expect(brokenSseController.send).toHaveBeenCalledTimes(1);
|
|
292
|
+
expect(cloudPermissionServer.session.sseController).toBeNull();
|
|
293
|
+
|
|
294
|
+
const secondResult = await client.requestPermission({
|
|
295
|
+
options: ALLOW_OPTIONS,
|
|
296
|
+
toolCall: { _meta: { codeToolKind: "bash" } },
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(secondResult.outcome.outcome).toBe("selected");
|
|
300
|
+
expect(brokenSseController.send).toHaveBeenCalledTimes(1);
|
|
301
|
+
expect(appendRawLine).toHaveBeenCalledTimes(2);
|
|
302
|
+
});
|
|
238
303
|
});
|
|
239
304
|
});
|
|
240
305
|
|