Skip to content

Commit fdf6a5d

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/onepassword-integration
2 parents c7bf6f1 + 29baf66 commit fdf6a5d

122 files changed

Lines changed: 24972 additions & 4430 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.test

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ STRIPE_KILOCLAW_EARLYBIRD_COUPON_ID=coupon_test_kiloclaw_earlybird
7979
STRIPE_KILOCLAW_COMMIT_PRICE_ID=price_test_kiloclaw_commit
8080
STRIPE_KILOCLAW_STANDARD_PRICE_ID=price_test_kiloclaw_standard
8181
STRIPE_KILOCLAW_STANDARD_FIRST_MONTH_COUPON_ID=coupon_test_kiloclaw_standard_first_month
82-
STRIPE_KILOCLAW_BILLING_START=
8382

8483
# Stripe publishable key for client-side 3DS authentication
8584
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_invalid_mock_key

.specs/kiloclaw-billing.md

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
Draft -- generated from branch `jdp/kiloclaw-billing` on 2026-03-13.
66
Updated 2026-03-19 -- pricing and trial duration changes.
7+
Updated 2026-03-20 -- promotional codes and introductory pricing.
78

89
## Conventions
910

@@ -42,6 +43,22 @@ lapses, with email notifications at each stage.
4243
7. The system MUST fail with an error at checkout time if a required
4344
plan price identifier is not configured.
4445

46+
### Standard Plan Introductory Pricing
47+
48+
1. New standard plan subscribers who do not have a prior canceled paid
49+
subscription MUST receive an introductory price for their first
50+
billing period. A canceled trial does not count as a prior paid
51+
subscription. Returning subscribers with a previously canceled paid
52+
subscription MUST receive the regular standard price.
53+
2. The system MUST automatically transition introductory-price
54+
subscribers to the regular standard price at the end of the
55+
introductory billing period.
56+
3. The automatic price transition MUST be transparent to the user: it
57+
MUST NOT appear as a pending plan switch in billing status and MUST
58+
NOT prevent the user from initiating a plan switch or canceling.
59+
4. Failure to set up the automatic price transition during subscription
60+
creation MUST NOT block checkout completion.
61+
4562
### Trial Eligibility and Creation
4663

4764
1. A trial MUST only be created automatically when a user provisions an
@@ -81,21 +98,18 @@ lapses, with email notifications at each stage.
8198
2. The system MUST allow checkout when the existing subscription status
8299
is trialing or canceled.
83100
3. The system MUST verify with the payment provider that no subscription
84-
in active or trialing (delayed-billing) status already exists for the
85-
customer before creating a new checkout session, to guard against
86-
concurrent checkouts. This check does not cover provider-side
87-
subscriptions in past-due status.
88-
4. The system MUST NOT allow promotional codes for either plan.
89-
5. The system MUST apply a provider-configured first-month discount
90-
coupon when creating a standard plan checkout session.
91-
6. When a configurable billing start date is set and is in the future,
92-
the system MUST create the subscription with a delayed billing period
93-
that begins on that date.
94-
7. When the billing start date is unset or is in the past, the system
95-
MUST start billing immediately with no delayed period.
96-
8. The system SHOULD include referral tracking data in checkout sessions
101+
in active or trialing status already exists for the customer before
102+
creating a new checkout session, to guard against concurrent
103+
checkouts. This check does not cover provider-side subscriptions in
104+
past-due status.
105+
4. The system MUST allow promotional codes on checkout for both plans.
106+
5. For standard plan checkout, the system MUST use the introductory
107+
price when the user has no prior canceled paid subscription, and the
108+
regular price when the user has a previously canceled paid
109+
subscription (see Standard Plan Introductory Pricing).
110+
6. The system SHOULD include referral tracking data in checkout sessions
97111
when a referral cookie is present.
98-
9. The system SHOULD attempt to expire open checkout sessions tagged as
112+
7. The system SHOULD attempt to expire open checkout sessions tagged as
99113
KiloClaw before creating a new checkout session, so users who
100114
abandoned a previous checkout can start fresh. Expiration is
101115
best-effort: errors from the payment provider (e.g. the session was
@@ -111,9 +125,8 @@ lapses, with email notifications at each stage.
111125
the subscription to the standard plan.
112126
2. When a commit subscription is created, the system MUST record a
113127
commit-period end date six calendar months from the billing start.
114-
When a delayed-billing period is configured, the six months MUST
115-
start from the delayed-billing end date, not from subscription
116-
creation.
128+
For pre-launch subscriptions that had a delayed-billing trial_end,
129+
the six months starts from that trial boundary.
117130
3. When a subscription update is received and the commit-period end
118131
date is in the past, the system MUST extend it by six calendar
119132
months from the previous boundary, keeping the subscription on the
@@ -145,21 +158,35 @@ lapses, with email notifications at each stage.
145158
the commit-period end date to six calendar months from the
146159
transition date.
147160
8. The system MUST allow cancellation of user-initiated plan switches.
161+
9. The system MUST reject a plan switch if a user-initiated plan switch
162+
is already pending.
163+
10. If an automatic price transition is pending when the user requests a
164+
plan switch, the system MUST replace the automatic transition with
165+
the user's requested switch.
166+
11. After canceling a user-initiated plan switch, if the subscription is
167+
still on the introductory price, the system MUST restore the
168+
automatic price transition. Failure to restore the transition MUST
169+
NOT prevent the switch cancellation from succeeding.
148170

149171
### Cancellation and Reactivation
150172

151173
1. The system MUST reject a cancellation request if no active
152174
subscription with a payment provider ID exists.
153175
2. The system MUST reject a cancellation request if cancellation is
154176
already pending.
155-
3. When canceling a subscription that has a pending schedule, the system
156-
MUST release the schedule before setting the cancel-at-period-end
157-
flag.
177+
3. When canceling a subscription that has a pending schedule — whether
178+
a user-initiated plan switch or an automatic price transition — the
179+
system MUST release the schedule before setting the
180+
cancel-at-period-end flag.
158181
4. Cancellation MUST NOT terminate access immediately; access MUST
159182
continue until the current billing period ends.
160183
5. The system MUST allow reactivation of a subscription that is pending
161184
cancellation.
162185
6. On reactivation, the system MUST clear the cancel-at-period-end flag.
186+
7. On reactivation, if the subscription is still on the introductory
187+
price, the system MUST restore the automatic price transition.
188+
Failure to restore the transition MUST NOT prevent the reactivation
189+
from succeeding.
163190

164191
### Billing Lifecycle Background Job
165192

@@ -169,6 +196,9 @@ lapses, with email notifications at each stage.
169196
2. Each sweep in the background job MUST process users independently;
170197
a failure for one user MUST NOT prevent processing of other users.
171198
3. All errors during sweep processing MUST be captured for monitoring.
199+
4. The background job MUST detect active subscriptions on the
200+
introductory price that have no automatic price transition pending
201+
and MUST set up the missing transition.
172202

173203
### Trial Expiry Warnings
174204

@@ -258,8 +288,9 @@ lapses, with email notifications at each stage.
258288
### Payment Provider Status Mapping
259289

260290
1. When the payment provider reports a subscription as "trialing"
261-
(delayed billing), the system MUST map this to active status
262-
internally, since delayed billing is not a product-level trial.
291+
(e.g. pre-launch delayed billing), the system MUST map this to
292+
active status internally, since delayed billing is not a
293+
product-level trial.
263294
2. When the payment provider reports "incomplete" or "paused" status,
264295
the system MUST map these to terminal statuses (unpaid or canceled
265296
respectively).
@@ -299,6 +330,28 @@ lapses, with email notifications at each stage.
299330

300331
### Changelog
301332

333+
#### 2026-03-21 -- Remove delayed-billing start date
334+
335+
- Removed configurable billing start date (`STRIPE_KILOCLAW_BILLING_START`)
336+
and associated checkout rules (former rules 6–7). New subscriptions now
337+
always bill immediately.
338+
- Pre-launch subscriptions created with a delayed `trial_end` are still
339+
handled correctly; the trialing→active status mapping remains until those
340+
subscriptions transition.
341+
342+
#### 2026-03-20 -- Promotional codes and introductory pricing
343+
344+
Previous values:
345+
346+
- Standard plan first-month discount: coupon applied at checkout
347+
- Promotional codes: not allowed on either plan
348+
349+
New values:
350+
351+
- Standard plan first-month discount: introductory price with automatic
352+
transition to regular price at period end
353+
- Promotional codes: allowed on both plans at checkout
354+
302355
#### 2026-03-19 -- Pricing and trial changes
303356

304357
Previous values:

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ When creating or updating a pull request, you **must** follow the PR template in
5252
- Do not leave HTML comments from the template in the final description — replace them with actual content.
5353
- PR descriptions must be accurate and valuable to reviewers. Generic or boilerplate descriptions waste reviewer time.
5454
- Review all commits on the branch (not just the latest) when writing the summary.
55+
56+
## KiloClaw Billing
57+
58+
Before making **any** changes related to KiloClaw billing — including bug fixes, new features, refactors, or reviews — you **must** first read the billing spec at `.specs/kiloclaw-billing.md`. This applies to all code paths that touch billing logic, pricing, invoicing, usage metering, or payment flows.

cloud-agent-next/wrapper/src/connection.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,28 @@ export function createConnectionManager(
273273
return false;
274274
}
275275

276+
function getTerminalErrorText(eventType: string, properties: Record<string, unknown>): string {
277+
const error = properties.error;
278+
if (typeof error === 'string') {
279+
return error;
280+
}
281+
282+
if (isRecord(error)) {
283+
if (typeof error.message === 'string') {
284+
return error.message;
285+
}
286+
287+
const data = error.data;
288+
if (isRecord(data) && typeof data.message === 'string') {
289+
return data.message;
290+
}
291+
292+
return JSON.stringify(error);
293+
}
294+
295+
return `Insufficient credits: ${eventType}`;
296+
}
297+
276298
/**
277299
* Start the SDK event subscription. Runs in the background.
278300
* Replaces the old SSE consumer with a typed event stream from the SDK.
@@ -367,7 +389,7 @@ export function createConnectionManager(
367389

368390
// Terminal error detection
369391
if (isTerminalError(eventType, properties)) {
370-
callbacks.onTerminalError(eventType);
392+
callbacks.onTerminalError(getTerminalErrorText(eventType, properties));
371393
return;
372394
}
373395

cloudflare-gastown/AGENTS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,37 @@
88
## Durable Objects
99

1010
- Each DO module must export a `get{ClassName}Stub` helper function (e.g. `getRigDOStub`) that centralizes how that DO namespace creates instances. Callers should use this helper instead of accessing the namespace binding directly.
11+
- **Sub-modules for large DOs**: When a Durable Object grows beyond a few hundred lines, extract domain logic into sub-modules under a `<do-name>/` directory alongside the DO file. For example, `Town.do.ts` delegates to modules in `town/`:
12+
13+
```
14+
dos/
15+
Town.do.ts # Class definition, RPC methods, alarm loop
16+
town/
17+
agents.ts # Agent CRUD, hook management
18+
beads.ts # Bead CRUD, convoy progress
19+
scheduling.ts # Agent dispatch, pending work scheduling
20+
review-queue.ts # Review lifecycle, recovery
21+
patrol.ts # Zombie detection, stale hook recovery
22+
config.ts # Town configuration
23+
rigs.ts # Rig registry
24+
mail.ts # Inter-agent mail
25+
container-dispatch.ts # Container start/stop/status
26+
```
27+
28+
Each sub-module exports plain functions (not classes) that accept `SqlStorage` and any other required context as arguments. The DO imports them with the `import * as X` pattern:
29+
30+
```ts
31+
import * as beadOps from './town/beads';
32+
import * as agents from './town/agents';
33+
import * as scheduling from './town/scheduling';
34+
35+
// In the DO class:
36+
beadOps.updateBeadStatus(this.sql, beadId, 'closed', agentId);
37+
agents.getOrCreateAgent(this.sql, 'polecat', rigId, this.townId);
38+
await scheduling.schedulePendingWork(this.schedulingCtx);
39+
```
40+
41+
This keeps the DO class thin (RPC surface + orchestration) while sub-modules own the business logic. The `import * as X` pattern makes call sites self-documenting — you can always tell which domain a function belongs to.
1142

1243
## IO boundaries
1344

cloudflare-gastown/container/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ RUN cd /opt/gastown-plugin && npm install --omit=dev && \
4444
ln -s /opt/gastown-plugin/index.ts /home/agent/.config/kilo/plugins/gastown.ts && \
4545
chown -R agent:agent /home/agent/.config
4646

47+
# ── Git config for agent user ───────────────────────────────────────
48+
# Skip LFS smudge filter: agents don't need binary assets and LFS
49+
# downloads can fail when credentials don't cover the batch endpoint.
50+
# Also disable LFS fetch entirely so clone/worktree never stalls.
51+
RUN printf '[filter "lfs"]\n\tsmudge = git-lfs smudge --skip -- %%f\n\tprocess = git-lfs filter-process --skip\n\tclean = git-lfs clean -- %%f\n\trequired = true\n[lfs]\n\tfetchexclude = *\n' \
52+
> /home/agent/.gitconfig && \
53+
chown agent:agent /home/agent/.gitconfig
54+
4755
WORKDIR /app
4856

4957
# ── Install production deps via pnpm ────────────────────────────────

cloudflare-gastown/container/package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88
"start": "bun run src/main.ts",
99
"test": "vitest run",
1010
"test:watch": "vitest",
11-
"typecheck": "tsgo --noEmit --incremental false",
12-
"lint": "pnpm run lint:oxlint && pnpm run lint:eslint:fallback",
13-
"lint:oxlint": "pnpm -w exec oxlint --config .oxlintrc.json cloudflare-gastown/container/src",
14-
"lint:eslint:fallback": "eslint --config eslint.config.mjs --cache 'src/**/*.ts'"
11+
"typecheck": "tsc --noEmit",
12+
"lint": "eslint --config eslint.config.mjs --cache 'src/**/*.ts'"
1513
},
1614
"dependencies": {
1715
"@kilocode/plugin": "7.0.37",
@@ -21,8 +19,7 @@
2119
},
2220
"devDependencies": {
2321
"@kilocode/eslint-config": "workspace:*",
24-
"@types/bun": "^1.3.10",
25-
"@typescript/native-preview": "catalog:",
22+
"@types/bun": "^1.2.17",
2623
"eslint": "catalog:",
2724
"typescript": "catalog:",
2825
"vitest": "^3.2.4"

cloudflare-gastown/container/plugin/client.ts

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ export class GastownClient {
116116
});
117117
}
118118

119+
async requestChanges(input: {
120+
feedback: string;
121+
files?: string[];
122+
}): Promise<{ rework_bead_id: string }> {
123+
return this.request<{ rework_bead_id: string }>(this.agentPath('/request-changes'), {
124+
method: 'POST',
125+
body: JSON.stringify(input),
126+
});
127+
}
128+
119129
async checkMail(): Promise<Mail[]> {
120130
return this.request<Mail[]>(this.agentPath('/mail'));
121131
}
@@ -300,8 +310,6 @@ export class MayorGastownClient {
300310
title: string;
301311
body?: string;
302312
metadata?: Record<string, unknown>;
303-
depends_on?: string[];
304-
convoy_id?: string;
305313
}): Promise<SlingResult> {
306314
return this.request<SlingResult>(this.mayorPath('/sling'), {
307315
method: 'POST',
@@ -383,35 +391,6 @@ export class MayorGastownClient {
383391
);
384392
}
385393

386-
async addBeadDependency(input: {
387-
rig_id: string;
388-
bead_id: string;
389-
depends_on_bead_id: string;
390-
dependency_type?: 'blocks' | 'tracks' | 'parent-child';
391-
}): Promise<void> {
392-
await this.request<{ ok: true }>(
393-
`${this.baseUrl}/api/towns/${this.townId}/rigs/${input.rig_id}/beads/${input.bead_id}/dependencies`,
394-
{
395-
method: 'POST',
396-
body: JSON.stringify({
397-
depends_on_bead_id: input.depends_on_bead_id,
398-
dependency_type: input.dependency_type,
399-
}),
400-
}
401-
);
402-
}
403-
404-
async removeBeadDependency(input: {
405-
rig_id: string;
406-
bead_id: string;
407-
depends_on_bead_id: string;
408-
}): Promise<void> {
409-
await this.request<{ ok: true; deleted: boolean }>(
410-
`${this.baseUrl}/api/towns/${this.townId}/rigs/${input.rig_id}/beads/${input.bead_id}/dependencies/${input.depends_on_bead_id}`,
411-
{ method: 'DELETE' }
412-
);
413-
}
414-
415394
async listConvoys(): Promise<Convoy[]> {
416395
return this.request<Convoy[]>(this.mayorPath('/convoys'));
417396
}
@@ -429,7 +408,6 @@ export class MayorGastownClient {
429408
status?: 'open' | 'in_progress' | 'in_review' | 'closed' | 'failed';
430409
priority?: 'low' | 'medium' | 'high' | 'critical';
431410
labels?: string[];
432-
convoy_id?: string | null;
433411
}
434412
): Promise<Bead> {
435413
return this.request<Bead>(this.mayorPath(`/rigs/${rigId}/beads/${beadId}`), {

0 commit comments

Comments
 (0)