Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 2cbabdd

Browse files
committed
Merge remote-tracking branch 'origin/main' into web-bind-pi-runner-stub-enricher
2 parents 71ba99f + 7b193db commit 2cbabdd

16 files changed

Lines changed: 581 additions & 121 deletions

‎apps/code/snapshots.yml‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -561,17 +561,17 @@ snapshots:
561561
git-dialogs--sync-success--light:
562562
hash: v1.k4693efd2.8d79d77c9338aeaa73734836f0e17fde987f6d3239914f4013889d3110e5088d.Rq-w2o1uhes_a5hUWqMXeiDkfnoPCZPt932LkEQXCco
563563
loops-loopslistview--comprehensive--dark:
564-
hash: v1.k4693efd2.3c2a6b87092674a3d395a6f5d36889ea231696d788b159f357be0926f3804c8a.Ihzlq7XIy9rRoA7DTbpLePlBhjP34o8ppgP__NXx-Yw
564+
hash: v1.k4693efd2.b9223e477701b5ef4587310a1de71500dd5214222531648ef76d66766b049a9f.2lckIm16WmmwZv1xVZnUL42InIHDzemQwU3EJzzw9_U
565565
loops-loopslistview--comprehensive--light:
566-
hash: v1.k4693efd2.42c2994def56651de08f5be20e98dbe67bfbdb03e7edd8bdf008401468c1653a.EnH7NwNkvclS7g5Htf7OCZuYEm7fI5W6hEr4DmwZC8I
566+
hash: v1.k4693efd2.1f172c831aefdfd4b3bae97af84f80aae3c09c4919f6b1b68315240cc482af50.hnrwbv8il_Ay6DcYxmQXNtxKU3rqOIWAkjnGB8n4AT4
567567
loops-loopslistview--long-mixed-list--dark:
568-
hash: v1.k4693efd2.384486583f8d9b66a4bf1d9cc45036a07310cb9e1943ea5b52ff466f55a03d7b.F_sXrJ13XWHzAF8yhoKun_k4BY8Jcb3EDtT0L1I6nKw
568+
hash: v1.k4693efd2.654250bc74fff46c496747eb13031e0e7a7ef264be5bb7be41451d82a497a515.vvrxXbbABkVY3hcHS3oThQ339JFgwIzB8E4n1usVWf0
569569
loops-loopslistview--long-mixed-list--light:
570-
hash: v1.k4693efd2.d80a0c55c4490276df53f781ac88ed9d9c6ec282bc14ddb80140b3ddcc44d32f.L6hw9ZixUiyTFKOybYzmjfUSIOayvmhueMTcmYt-IWo
570+
hash: v1.k4693efd2.a8e53087efdaf58e351809775aaf410f53a781a97e10e973ee53fd0db2f94e73.7GcprWAYAgATLtNckNBd-44EyctqKNcb4FXiHA08QJs
571571
loops-loopslistview--with-builder-sessions--dark:
572-
hash: v1.k4693efd2.bc59812bf49ff161cf20e75dcf043367ae3d2817aa2c2cf91c91d9771c6b62f8.yOBz-llmwDVay7GIHlMwlMT3EuKjkoW0Vwmwvi4noCM
572+
hash: v1.k4693efd2.84cfc28bedc22a6728ba4ecf69274ab03537faeed5333775991716bf0b5f439e.fHsD0XKPnud-oDM4PLJ7edCbMnVcW6uVMdMHBYp3K08
573573
loops-loopslistview--with-builder-sessions--light:
574-
hash: v1.k4693efd2.fb97e3fe3205ddf71d98c4e35446c73e394aaa9761d3bb35b146ddafc438df4d.UUEWc0G12NQibZTyskIHGS62mB36gKJ594GrJp1E9II
574+
hash: v1.k4693efd2.fb6c9b85bd660a6be4645cf7ba8fe3ab08a169e3823e5354325b7d7ecbe13aba.4FbKrdxiUN1PZOGwFa_kl7Rpz3Z8DnTVNJvUM_3FIp0
575575
scouts-scoutsfleetlist--filtered-to-you--dark:
576576
hash: v1.k4693efd2.a3af6e76ef36598eaa347bedc4430878ed62754660166398e0576a9978cd084b.x3Ky-O6HOw0srWdOmnnKJwFNpmGmQVWip0t7CwVaRXY
577577
scouts-scoutsfleetlist--filtered-to-you--light:

‎packages/core/src/sessions/sessionService.ts‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
type StoredLogEntry,
3636
sendableQueuePrefixLength,
3737
sessionSupportsNativeSteer,
38+
type TaskRunArtifact,
3839
type TaskRunStatus,
3940
} from "@posthog/shared";
4041
import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events";
@@ -1599,7 +1600,7 @@ export class SessionService {
15991600
/** Deduplicates concurrent manifest reads when a message renders many images. */
16001601
private cloudAttachmentManifestRequests = new Map<
16011602
string,
1602-
Promise<Array<{ id?: string; storage_path?: string }>>
1603+
Promise<TaskRunArtifact[]>
16031604
>();
16041605
private idleKilledSubscription: { unsubscribe: () => void } | null = null;
16051606
/**
@@ -4673,6 +4674,7 @@ export class SessionService {
46734674
cloudStatus: run.status,
46744675
cloudStage: run.stage ?? null,
46754676
cloudOutput: run.output ?? null,
4677+
cloudArtifacts: run.artifacts ?? [],
46764678
cloudErrorMessage: run.error_message,
46774679
logUrl: run.log_url ?? session.logUrl,
46784680
});
@@ -7293,22 +7295,34 @@ export class SessionService {
72937295
}
72947296
}
72957297

7298+
async getCloudRunArtifacts(
7299+
taskId: string,
7300+
runId: string,
7301+
): Promise<TaskRunArtifact[]> {
7302+
const authStatus = await this.getAuthCredentialsStatus();
7303+
if (authStatus.kind !== "ready") return [];
7304+
7305+
return this.getCloudAttachmentManifest(
7306+
authStatus.auth.client,
7307+
`${authStatus.auth.apiHost}:${authStatus.auth.projectId}`,
7308+
taskId,
7309+
runId,
7310+
);
7311+
}
7312+
72967313
private getCloudAttachmentManifest(
72977314
client: AuthClient,
72987315
authIdentity: string,
72997316
taskId: string,
73007317
runId: string,
7301-
): Promise<Array<{ id?: string; storage_path?: string }>> {
7318+
): Promise<TaskRunArtifact[]> {
73027319
const key = `${authIdentity}:${taskId}:${runId}`;
73037320
const existing = this.cloudAttachmentManifestRequests.get(key);
73047321
if (existing) return existing;
73057322

73067323
const request = client
73077324
.getTaskRun(taskId, runId)
7308-
.then(
7309-
(run: { artifacts?: Array<{ id?: string; storage_path?: string }> }) =>
7310-
run.artifacts ?? [],
7311-
);
7325+
.then((run: { artifacts?: TaskRunArtifact[] }) => run.artifacts ?? []);
73127326
this.cloudAttachmentManifestRequests.set(key, request);
73137327

73147328
const clear = () => {

‎packages/shared/src/domain-types.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Adapter } from "./adapter";
33
import type { AgentRuntime } from "./agent-runtime";
44
import type { DismissalReasonOptionValue } from "./dismissal-reasons";
55
import type { StoredLogEntry } from "./session-events";
6+
import type { TaskRunArtifact } from "./task";
67

78
// Execution mode schema and type - shared between main and renderer
89
export const executionModeSchema = z.enum([
@@ -180,6 +181,7 @@ export interface TaskRun {
180181
error_message: string | null;
181182
output: Record<string, unknown> | null; // Structured output (PR URL, commit SHA, etc.)
182183
state: Record<string, unknown>; // Intermediate run state (defaults to {}, never null)
184+
artifacts?: TaskRunArtifact[];
183185
created_at: string;
184186
updated_at: string;
185187
completed_at: string | null;

‎packages/shared/src/sessions.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Adapter } from "./adapter";
1010
import type { SkillButtonId } from "./analytics-events";
1111
import type { ExecutionMode } from "./exec-types";
1212
import type { AcpMessage } from "./session-events";
13-
import type { TaskRunStatus } from "./task";
13+
import type { TaskRunArtifact, TaskRunStatus } from "./task";
1414

1515
export type { Adapter };
1616

@@ -94,6 +94,7 @@ export interface AgentSession {
9494
cloudStatus?: TaskRunStatus;
9595
cloudStage?: string | null;
9696
cloudOutput?: Record<string, unknown> | null;
97+
cloudArtifacts?: TaskRunArtifact[];
9798
cloudErrorMessage?: string | null;
9899
initialPrompt?: ContentBlock[];
99100
cloudBranch?: string | null;

‎packages/ui/src/features/canvas/components/WebsiteChannelLoops.tsx‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,15 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
8181
<Flex
8282
direction="column"
8383
gap="6"
84-
className="mx-auto w-full max-w-3xl px-8 py-8"
84+
className="@container mx-auto w-full max-w-3xl px-8 py-8"
8585
>
86-
<Flex align="center" justify="between" gap="3">
87-
<Flex direction="column" gap="1" className="min-w-0 max-w-[70%]">
88-
<Flex align="center" gap="2">
86+
<div className="flex @min-[640px]:flex-row flex-col items-start @min-[640px]:items-center justify-between gap-3">
87+
<Flex
88+
direction="column"
89+
gap="1"
90+
className="w-full min-w-0 @min-[640px]:max-w-[70%]"
91+
>
92+
<Flex align="center" gap="2" wrap="wrap">
8993
<Heading className="font-bold text-2xl">
9094
Automate #{contextName}
9195
</Heading>
@@ -119,7 +123,7 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
119123
<PlusIcon size={14} />
120124
Create manually
121125
</Button>
122-
</Flex>
126+
</div>
123127

124128
{isLoading ? (
125129
<LoopsSkeleton />
@@ -140,7 +144,11 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
140144
</Flex>
141145
</Flex>
142146
) : (
143-
<LoopsEmptyState contextName={contextName} />
147+
<LoopsEmptyState
148+
contextName={contextName}
149+
onCreate={startBlank}
150+
disabledReason={limitReason}
151+
/>
144152
)}
145153
</Flex>
146154
</div>

‎packages/ui/src/features/loops/components/LoopRow.tsx‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CaretRightIcon, RepeatIcon } from "@phosphor-icons/react";
22
import type { LoopSchemas } from "@posthog/api-client/loops";
3+
import { formatRelativeTimeLong } from "@posthog/shared";
34
import type { UserBasic } from "@posthog/shared/domain-types";
45
import { userDisplayName } from "@posthog/ui/features/canvas/utils/userDisplay";
56
import { Badge } from "@posthog/ui/primitives/Badge";
@@ -71,11 +72,19 @@ export function LoopRow({
7172
{description}
7273
</Text>
7374
) : null}
74-
{metadata.length > 0 ? (
75-
<Text className="mt-0.5 text-(--accent-11) text-[11px] leading-snug">
76-
{metadata.join(" · ")}
75+
<Flex align="center" gap="1" wrap="wrap" className="mt-0.5">
76+
{metadata.length > 0 ? (
77+
<Text className="text-(--accent-11) text-[11px] leading-snug">
78+
{metadata.join(" · ")}
79+
</Text>
80+
) : null}
81+
{metadata.length > 0 ? (
82+
<Text className="text-[11px] text-gray-9">·</Text>
83+
) : null}
84+
<Text className="text-[11px] text-gray-9">
85+
Created {formatRelativeTimeLong(loop.created_at)}
7786
</Text>
78-
) : null}
87+
</Flex>
7988
</Flex>
8089
</Flex>
8190
<Flex align="center" gap="3" className="shrink-0">

‎packages/ui/src/features/loops/components/LoopTemplatesSection.tsx‎

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export function LoopTemplatesSection({
2828
useState<LoopTemplateCategory>("engineering");
2929

3030
return (
31-
<Flex direction="column" gap="3">
32-
<Flex align="center" justify="between" gap="3">
31+
<div className="@container flex flex-col gap-3">
32+
<div className="flex @min-[480px]:flex-row flex-col items-start @min-[480px]:items-center justify-between gap-3">
3333
<Text className="font-medium text-[12px] text-gray-10 uppercase tracking-wide">
3434
Start from a template
3535
</Text>
@@ -49,19 +49,20 @@ export function LoopTemplatesSection({
4949
</button>
5050
))}
5151
</Flex>
52-
</Flex>
53-
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
52+
</div>
53+
<div className="grid @min-[640px]:grid-cols-2 grid-cols-1 gap-3">
5454
{LOOP_TEMPLATES.filter(
5555
(template) => template.category === templateCategory,
5656
).map((template) => (
57-
<TemplateCard
58-
key={template.id}
59-
template={template}
60-
onSelect={() => onSelect(template)}
61-
/>
57+
<div key={template.id} className="@container">
58+
<TemplateCard
59+
template={template}
60+
onSelect={() => onSelect(template)}
61+
/>
62+
</div>
6263
))}
6364
</div>
64-
</Flex>
65+
</div>
6566
);
6667
}
6768

@@ -80,7 +81,7 @@ function TemplateCard({
8081
<button
8182
type="button"
8283
onClick={onSelect}
83-
className="flex items-start gap-2.5 rounded-(--radius-3) border border-border bg-(--color-panel-solid) p-3 text-left transition-colors hover:border-(--gray-6) hover:bg-(--gray-2)"
84+
className="@container flex h-full w-full @min-[300px]:flex-row flex-col items-start gap-2.5 overflow-hidden rounded-(--radius-3) border border-border bg-(--color-panel-solid) p-3 text-left transition-colors hover:border-(--gray-6) hover:bg-(--gray-2)"
8485
>
8586
<Flex
8687
align="center"
@@ -97,20 +98,17 @@ function TemplateCard({
9798
{template.description}
9899
</Text>
99100
<Flex
100-
align="center"
101-
justify="between"
101+
align="start"
102102
gap="3"
103-
className="mt-0.5 w-full text-(--accent-11)"
103+
className="mt-0.5 w-full @min-[400px]:flex-row flex-col text-(--accent-11)"
104104
>
105105
<Flex align="center" className="min-w-0 gap-1.5">
106106
<TriggerIcon size={11} className="shrink-0" />
107-
<Text className="truncate text-[11px]">
108-
{template.triggerLabel}
109-
</Text>
107+
<Text className="text-[11px]">{template.triggerLabel}</Text>
110108
</Flex>
111-
<Flex align="center" className="shrink-0 gap-1.5">
109+
<Flex align="center" className="min-w-0 gap-1.5">
112110
<PlugsIcon size={11} className="shrink-0" />
113-
<Text className="text-[11px]">
111+
<Text className="min-w-0 text-[11px]">
114112
Works with {template.worksWith.join(" · ")}
115113
</Text>
116114
</Flex>

‎packages/ui/src/features/loops/components/LoopTriggerEditor.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ function ScheduleTriggerFields({
421421
type="time"
422422
disabled={disabled}
423423
value={time}
424-
className="h-8 rounded-(--radius-2) border border-border bg-(--color-panel-solid) px-2.5 text-[13px] text-gray-12"
424+
className="h-8 rounded-(--radius-2) border border-border bg-transparent px-2.5 text-[13px] text-gray-12"
425425
onChange={(e) => {
426426
if (!e.target.value) return;
427427
setRecurring(frequency, e.target.value, weekday);
@@ -448,7 +448,7 @@ function ScheduleTriggerFields({
448448
<input
449449
type="datetime-local"
450450
disabled={disabled}
451-
className="h-8 rounded-(--radius-2) border border-border bg-(--color-panel-solid) px-2.5 text-[13px] text-gray-12"
451+
className="h-8 rounded-(--radius-2) border border-border bg-transparent px-2.5 text-[13px] text-gray-12"
452452
value={config.run_at ? toDatetimeLocal(config.run_at) : ""}
453453
onChange={(e) =>
454454
onChange({
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Theme } from "@radix-ui/themes";
2+
import { render, screen } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
4+
import { describe, expect, it, vi } from "vitest";
5+
import { LoopsEmptyState } from "./LoopsEmptyState";
6+
7+
describe("LoopsEmptyState", () => {
8+
it("starts loop creation from the primary CTA", async () => {
9+
const onCreate = vi.fn();
10+
render(
11+
<Theme>
12+
<LoopsEmptyState onCreate={onCreate} />
13+
</Theme>,
14+
);
15+
16+
await userEvent.click(
17+
screen.getByRole("button", { name: "Create a loop" }),
18+
);
19+
20+
expect(onCreate).toHaveBeenCalledOnce();
21+
});
22+
23+
it("disables creation when the project reached its loop limit", () => {
24+
render(
25+
<Theme>
26+
<LoopsEmptyState
27+
onCreate={vi.fn()}
28+
disabledReason="This project reached its loop limit."
29+
/>
30+
</Theme>,
31+
);
32+
33+
expect(
34+
screen.getByRole("button", { name: "Create a loop" }),
35+
).toBeDisabled();
36+
});
37+
});

0 commit comments

Comments
 (0)