Skip to content
168 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge remote-tracking branch 'origin/main' into workspace-chat1import assert from "node:assert/strict";
2import { test } from "node:test";
3
Billing is ready to take real money, and stays on Stripe's test key until the switch. The g1t plan is $21 a month, all-in, with $10 of usage included: the card processing fee is part of the price, and no payment carries a fee line any more (old fee rows still read as they were). Card, billing address, tax IDs, invoices and cancelling are on Stripe's billing page (Manage billing), whose configuration billing keeps in step; Checkout asks for the billing address and a tax ID; g1t's Billing page keeps usage, the spend limit, budget alerts and prepaying. Members other than owners and billing managers no longer see billing details or invoices, from the page or the API.4import {
5 answered,
6 capReached,
7 describeError,
8 OWED_GIVE_UP_MS,
9 OWED_MAX_DELAY_SECONDS,
10 owedDelaySeconds,
11 recordOwed,
12 stoppedShort,
13 tellStopped,
14 withinTimeCap,
15} from "./lifecycle.ts";
Merge remote-tracking branch 'origin/main' into workspace-chat16
17const quiet = { waitMs: 0 };
18
19test("a stop that is told the first time is told once and logs nothing", async () => {
20 const logged: unknown[][] = [];
21 let calls = 0;
22 const told = await tellStopped("checks", async () => void calls++, { ...quiet, log: (...data) => logged.push(data) });
23 assert.equal(told, true);
24 assert.equal(calls, 1);
25 assert.deepEqual(logged, []);
26});
27
28test("a stop that fails once is tried again, and gets through", async () => {
29 const logged: unknown[][] = [];
30 let calls = 0;
31 const told = await tellStopped(
32 "queue",
33 async () => {
34 calls++;
35 if (calls === 1) throw new Error("report_queue failed with status 500");
36 },
37 { ...quiet, log: (...data) => logged.push(data) },
38 );
39 assert.equal(told, true);
40 assert.equal(calls, 2);
41 assert.deepEqual(logged, []);
42});
43
44test("a stop that cannot be told never throws, and is logged with what and why", async () => {
45 const logged: unknown[][] = [];
46 let calls = 0;
47 const told = await tellStopped(
48 "actions",
49 async () => {
50 calls++;
51 throw new Error("job_report answered 503");
52 },
53 { ...quiet, log: (...data) => logged.push(data) },
54 );
55 assert.equal(told, false);
56 assert.equal(calls, 2);
57 assert.deepEqual(logged, [["sandbox stop not reported", "actions", "job_report answered 503"]]);
58});
59
60test("whatever is thrown, even nothing, is logged as one line", async () => {
61 const logged: unknown[][] = [];
62 await tellStopped("plan", () => Promise.reject(undefined), { ...quiet, attempts: 1, log: (...data) => logged.push(data) });
63 assert.deepEqual(logged, [["sandbox stop not reported", "plan", "no reason given"]]);
64 assert.equal(describeError("gone"), "gone");
65 assert.equal(describeError({ code: 7 }), '{"code":7}');
66 assert.equal(describeError(new TypeError("bad")), "bad");
67});
68
69test("a service that cannot answer is a failure; one that refuses is not", async () => {
70 await answered("job_report", new Response("{}", { status: 200 }));
71 // The deployments service's answer for a build that already finished.
72 await answered("deploy fail", new Response("{}", { status: 404 }));
73 await answered("job_report", new Response("{}", { status: 409 }));
74 await assert.rejects(answered("job_report", new Response("worker threw", { status: 500 })), /job_report answered 500: worker threw/);
75 await assert.rejects(answered("deploy fail", new Response("", { status: 503 })), /^Error: deploy fail answered 503$/);
76 await assert.rejects(answered("job_report", new Response("slow down", { status: 429 })), /429/);
77});
78
79test("a run inside its time cap is not stopped for inactivity", () => {
80 const started = Date.UTC(2026, 9, 8, 12, 0, 0);
81 const minute = 60_000;
82 const grace = 180;
83 // A 240-minute cap outlives the 100-minute sleepAfter.
84 assert.equal(withinTimeCap(started, 240, started + 100 * minute, grace), true);
85 assert.equal(withinTimeCap(started, 240, started + 242 * minute, grace), true);
86 // Past the cap and its alarm's grace: the library may stop it.
87 assert.equal(withinTimeCap(started, 240, started + 243 * minute, grace), false);
88 assert.equal(withinTimeCap(started, 90, started + 100 * minute, grace), false);
89});
90
91test("a sandbox with no cap or no start is stopped for inactivity as before", () => {
92 const now = Date.now();
93 assert.equal(withinTimeCap(undefined, 90, now, 180), false);
94 assert.equal(withinTimeCap(now - 1000, undefined, now, 180), false);
95 assert.equal(withinTimeCap(now - 1000, null, now, 180), false);
96 assert.equal(withinTimeCap(now - 1000, 0, now, 180), false);
97});
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.98
99test("an earlier attempt's time cap does not stop the attempt running now", () => {
100 const minute = 60_000;
101 // Desktop release, Linux job: attempt 1 started 12:47:17 with a 60-minute
102 // cap, so its alarm fired at 13:50:17, four minutes into attempt 3 in the
103 // same Durable Object.
104 const attempt3 = Date.UTC(2026, 9, 12, 13, 46, 15);
105 const alarm = Date.UTC(2026, 9, 12, 13, 50, 17);
106 assert.equal(capReached(attempt3, 60, alarm), false);
107 // Its own alarm, at its cap and the alarm's grace, does stop it.
108 assert.equal(capReached(attempt3, 60, attempt3 + 63 * minute), true);
109 assert.equal(capReached(attempt3, 60, attempt3 + 60 * minute), true);
110 assert.equal(capReached(attempt3, 60, attempt3 + 60 * minute - 1), false);
111});
112
113test("a sandbox with no run or no cap is never stopped for time", () => {
114 const now = Date.now();
115 assert.equal(capReached(undefined, 60, now), false);
116 assert.equal(capReached(now - 120 * 60_000, undefined, now), false);
117 assert.equal(capReached(now - 120 * 60_000, 0, now), false);
118});
119
120test("a sandbox g1t stopped fails even when its exit code says 0", () => {
121 assert.equal(stoppedShort(0, null), false);
122 assert.equal(stoppedShort(0, "It ran into its time cap of 60 minutes."), true);
123 assert.equal(stoppedShort(137, null), true);
124 assert.equal(stoppedShort(1, "why"), true);
125});
Billing is ready to take real money, and stays on Stripe's test key until the switch. The g1t plan is $21 a month, all-in, with $10 of usage included: the card processing fee is part of the price, and no payment carries a fee line any more (old fee rows still read as they were). Card, billing address, tax IDs, invoices and cancelling are on Stripe's billing page (Manage billing), whose configuration billing keeps in step; Checkout asks for the billing address and a tax ID; g1t's Billing page keeps usage, the spend limit, budget alerts and prepaying. Members other than owners and billing managers no longer see billing details or invoices, from the page or the API.126
127test("sandbox time billing confirms is forgotten; the rest is kept and tried again later", async () => {
128 const now = 1_000_000_000;
129 const owed = [
130 { usage: { reference: "sandbox/a/1" }, attempts: 0, since: now },
131 { usage: { reference: "sandbox/b/2" }, attempts: 2, since: now - 1000 },
132 ];
133 const sent: string[] = [];
134 const result = await recordOwed(
135 owed,
136 async (usage) => {
137 sent.push(usage.reference);
138 if (usage.reference === "sandbox/b/2") throw new Error("billing answered 503");
139 return true;
140 },
141 now,
142 );
143 assert.deepEqual(sent, ["sandbox/a/1", "sandbox/b/2"]);
144 assert.deepEqual(result.recorded.map((entry) => entry.usage.reference), ["sandbox/a/1"]);
145 // The same usage, under the same reference: billing records it once.
146 assert.deepEqual(result.kept, [{ usage: { reference: "sandbox/b/2" }, attempts: 3, since: now - 1000 }]);
147 assert.deepEqual(result.lost, []);
148 assert.equal(result.retryInSeconds, owedDelaySeconds(3));
149});
150
151test("nothing kept means no retry; time tried for a week is given up", async () => {
152 const now = 1_000_000_000;
153 const done = await recordOwed([{ usage: { reference: "r" }, attempts: 0, since: now }], async () => true, now);
154 assert.equal(done.retryInSeconds, null);
155 const old = await recordOwed([{ usage: { reference: "r" }, attempts: 40, since: now - OWED_GIVE_UP_MS }], async () => false, now);
156 assert.deepEqual(old.kept, []);
157 assert.equal(old.lost.length, 1);
158 assert.equal(old.retryInSeconds, null);
159});
160
161test("tries back off from 30 seconds to an hour", () => {
162 assert.equal(owedDelaySeconds(0), 30);
163 assert.equal(owedDelaySeconds(1), 30);
164 assert.equal(owedDelaySeconds(2), 60);
165 assert.equal(owedDelaySeconds(5), 480);
166 assert.equal(owedDelaySeconds(8), OWED_MAX_DELAY_SECONDS);
167 assert.equal(owedDelaySeconds(1000), OWED_MAX_DELAY_SECONDS);
168});

This file's history is long; its oldest lines are credited to the oldest commit read.