Skip to content

Commit e811161

Browse files
committed
Merge branch 'main' of github.com:Kilo-Org/cloud into feature/kiloclaw-linear-cli
2 parents b466318 + 6eb1dff commit e811161

390 files changed

Lines changed: 30116 additions & 20796 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.

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ jobs:
115115
- name: Install dependencies
116116
run: pnpm install --frozen-lockfile
117117

118+
- name: Build trpc types
119+
run: pnpm --filter @kilocode/trpc run build
120+
118121
- name: Lint
119122
run: scripts/lint-all.sh
120123

.gitignore

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ node_modules/
3131
# production
3232
/build
3333

34+
# dev server port (written by scripts/dev.sh for multi-worktree support)
35+
.dev-port
36+
3437
# debug
3538
npm-debug.log*
3639
yarn-debug.log*
@@ -60,6 +63,7 @@ dev-debug-request-logs/*
6063

6164
# misc
6265
.DS_Store
66+
.eslintcache
6367
*.pem
6468
TMP_CI_commit_msg.txt
6569
*.csv
@@ -98,5 +102,5 @@ supabase/.temp
98102

99103
.kilo/plans
100104

101-
# @kilocode/trpc intermediate tsc output (bundled index.d.ts is committed)
102-
packages/trpc/dist/tsc/
105+
# @kilocode/trpc build output (rebuilt by: pnpm --filter @kilocode/trpc run build)
106+
packages/trpc/dist/

.kilocode/rules/tools.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
- Date handling: date-fns
88
- Database: drizzle-orm with pg
99
- For React components, ALWAYS use React hooks with @tanstack/react-query for data fetching, caching, and server state management. Do not implement custom data fetching logic when react-query can handle it.
10-
- DO NOT RUN `pnpm run dev`! The webserver should already be running; try simply accessing it first via 'http://localhost:3000/'. If it does not appear to be running, EVEN THEN ASK FOR PERMISSION BEFORE RUNNING IT.
11-
- If you need to log in as a fake user, open http://localhost:3000/users/sign_in?fakeUser=<fake-email> where <fake-email> is constructed from "kilo-", my username (based on homedir), and the time (include seconds) and then '@example.com'. If you need an admin account, the email address must end in @admin.example.com. After logging in, wait for the creating your account spinner to complete before proceeding. The admin panels can be accessed from your profile via the account icon in the top-right corner, which opens a drop-down, allowing access to the admin panel.
10+
- The webserver should already be running. Read the dev server port from the `.dev-port` file in the project root (e.g., `cat .dev-port`), then access it via `http://localhost:<port>/`. If `.dev-port` does not exist, start the dev server by running `pnpm dev &` in the background, then wait for `.dev-port` to appear before reading the port from it.
11+
- If you need to log in as a fake user, open http://localhost:<port>/users/sign_in?fakeUser=<fake-email> where <port> is read from `.dev-port` (falling back to 3000) and <fake-email> is constructed from "kilo-", my username (based on homedir), and the time (include seconds) and then '@example.com'. If you need an admin account, the email address must end in @admin.example.com. After logging in, wait for the creating your account spinner to complete before proceeding. The admin panels can be accessed from your profile via the account icon in the top-right corner, which opens a drop-down, allowing access to the admin panel.
1212
- be sure to add the callbackPath url parameter to go directly to the page after logging in!
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE `events` ADD COLUMN `entity_id` text;
2+
--> statement-breakpoint
3+
CREATE UNIQUE INDEX `events_entity_id_unique` ON `events` (`entity_id`);

cloud-agent-next/drizzle/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
"when": 1772242306388,
99
"tag": "0000_high_mimic",
1010
"breakpoints": true
11+
},
12+
{
13+
"idx": 1,
14+
"version": "6",
15+
"when": 1772242306389,
16+
"tag": "0001_add_entity_id",
17+
"breakpoints": true
1118
}
1219
]
1320
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import journal from './meta/_journal.json';
22
import m0000 from './0000_high_mimic.sql';
3+
import m0001 from './0001_add_entity_id.sql';
34

45
export default {
56
journal,
67
migrations: {
78
m0000,
9+
m0001,
810
},
911
};

cloud-agent-next/src/db/sqlite-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const events = sqliteTable(
99
stream_event_type: text('stream_event_type').notNull(),
1010
payload: text('payload').notNull(),
1111
timestamp: integer('timestamp').notNull(),
12+
entity_id: text('entity_id').unique(),
1213
},
1314
table => [
1415
index('idx_events_execution').on(table.execution_id),

cloud-agent-next/src/kilo/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export class KiloClient {
147147
modelID: options.model.modelID,
148148
}
149149
: undefined,
150+
variant: options?.variant,
150151
agent: options?.agent,
151152
noReply: options?.noReply,
152153
system: options?.system,

cloud-agent-next/src/kilo/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export interface CreateSessionOptions {
4949
export interface PromptOptions {
5050
messageId?: string;
5151
model?: { providerID?: string; modelID: string };
52+
/** Thinking/reasoning effort variant (e.g., "high", "max", "low") */
53+
variant?: string;
5254
agent?: string;
5355
noReply?: boolean;
5456
/** Custom system prompt override */

cloud-agent-next/src/persistence/CloudAgentSession.ts

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import {
5151
type IngestDOContext,
5252
} from '../websocket/ingest.js';
5353
import type { StoredEvent } from '../websocket/types.js';
54-
import type { WrapperCommand, PreparingStep } from '../shared/protocol.js';
54+
import type { WrapperCommand, PreparingStep, CloudStatusData } from '../shared/protocol.js';
5555
import { STALE_THRESHOLD_MS, SANDBOX_SLEEP_AFTER_SECONDS } from '../core/lease.js';
5656
import { ExecutionOrchestrator, type OrchestratorDeps } from '../execution/orchestrator.js';
5757
import type {
@@ -126,7 +126,6 @@ export class CloudAgentSession extends DurableObject {
126126
private ingestHandlerSessionId?: SessionId;
127127
private sessionId?: SessionId;
128128
private orchestrator?: ExecutionOrchestrator;
129-
130129
private isTerminalStatus(
131130
status: ExecutionStatus
132131
): status is 'completed' | 'failed' | 'interrupted' {
@@ -251,7 +250,9 @@ export class CloudAgentSession extends DurableObject {
251250
private async getStreamHandler(expected?: SessionId): Promise<StreamHandler> {
252251
const sessionId = await this.requireSessionId(expected);
253252
if (!this.streamHandler || this.streamHandlerSessionId !== sessionId) {
254-
this.streamHandler = createStreamHandler(this.ctx, this.eventQueries, sessionId);
253+
this.streamHandler = createStreamHandler(this.ctx, this.eventQueries, sessionId, {
254+
deriveCloudStatus: () => this.deriveCloudStatus(),
255+
});
255256
this.streamHandlerSessionId = sessionId;
256257
}
257258
return this.streamHandler;
@@ -366,7 +367,14 @@ export class CloudAgentSession extends DurableObject {
366367
}
367368

368369
const streamHandler = await this.getStreamHandler(sessionIdParam ?? undefined);
369-
return streamHandler.handleStreamRequest(request);
370+
const response = await streamHandler.handleStreamRequest(request);
371+
372+
// Request fresh kilo state from wrapper if connected.
373+
// The wrapper will respond with regular kilocode events (session.status,
374+
// question.asked, permission.asked) that are broadcast via the normal pipeline.
375+
this.requestKiloSnapshot();
376+
377+
return response;
370378
}
371379

372380
// Route ingest WebSocket (internal only - from queue consumer)
@@ -523,6 +531,28 @@ export class CloudAgentSession extends DurableObject {
523531
});
524532
}
525533

534+
/**
535+
* Derive current cloud infrastructure status from execution state.
536+
* Used to populate the `connected` event on WebSocket upgrade.
537+
*/
538+
private async deriveCloudStatus(): Promise<CloudStatusData['cloudStatus'] | null> {
539+
const activeExecId = await this.executionQueries.getActiveExecutionId();
540+
if (!activeExecId) {
541+
const metadata = await this.ctx.storage.get<CloudAgentSessionState>('metadata');
542+
return metadata?.preparedAt ? { type: 'ready' } : null;
543+
}
544+
545+
const exec = await this.executionQueries.get(activeExecId);
546+
if (!exec) return null;
547+
548+
if (exec.status === 'pending') {
549+
return { type: 'preparing' };
550+
}
551+
552+
// Running executions mean the agent has control — infrastructure is ready
553+
return { type: 'ready' };
554+
}
555+
526556
/**
527557
* Get count of connected stream clients.
528558
*
@@ -718,6 +748,19 @@ export class CloudAgentSession extends DurableObject {
718748
}
719749
}
720750

751+
/**
752+
* Request fresh kilo state from the wrapper.
753+
* The wrapper will respond with regular kilocode events (session.status,
754+
* question.asked, permission.asked) that flow through the normal ingest pipeline.
755+
* Best-effort: silently does nothing if no wrapper is connected.
756+
*/
757+
private requestKiloSnapshot(): void {
758+
void this.executionQueries.getActiveExecutionId().then(activeExecId => {
759+
if (!activeExecId) return;
760+
this.sendToWrapper(activeExecId, { type: 'request_snapshot' });
761+
});
762+
}
763+
721764
/**
722765
* Interrupt the currently active execution by sending a kill command to the wrapper.
723766
* Returns success/failure status.
@@ -903,12 +946,28 @@ export class CloudAgentSession extends DurableObject {
903946
const env = this.env as unknown as WorkerEnv;
904947

905948
const emitProgress = (step: PreparingStep, message: string) => {
949+
const now = Date.now();
950+
// Backward-compatible preparing event
906951
this.broadcastVolatileEvent({
907952
executionId: prepExecutionId,
908953
sessionId: input.sessionId,
909954
streamEventType: 'preparing',
910955
payload: JSON.stringify({ step, message }),
911-
timestamp: Date.now(),
956+
timestamp: now,
957+
});
958+
// cloud.status event derived from preparation step
959+
const cloudStatus =
960+
step === 'ready'
961+
? { type: 'ready' as const }
962+
: step === 'failed'
963+
? { type: 'error' as const, message }
964+
: { type: 'preparing' as const, step, message };
965+
this.broadcastVolatileEvent({
966+
executionId: prepExecutionId,
967+
sessionId: input.sessionId,
968+
streamEventType: 'cloud.status',
969+
payload: JSON.stringify({ cloudStatus }),
970+
timestamp: now,
912971
});
913972
};
914973

@@ -1193,15 +1252,21 @@ export class CloudAgentSession extends DurableObject {
11931252

11941253
if (this.sessionId) {
11951254
const prepId: EventSourceId = `prep_${this.sessionId}`;
1255+
const failNow = Date.now();
1256+
const failMessage = 'Internal error: invalid preparation data';
11961257
this.broadcastVolatileEvent({
11971258
executionId: prepId,
11981259
sessionId: this.sessionId,
11991260
streamEventType: 'preparing',
1200-
payload: JSON.stringify({
1201-
step: 'failed',
1202-
message: 'Internal error: invalid preparation data',
1203-
}),
1204-
timestamp: Date.now(),
1261+
payload: JSON.stringify({ step: 'failed', message: failMessage }),
1262+
timestamp: failNow,
1263+
});
1264+
this.broadcastVolatileEvent({
1265+
executionId: prepId,
1266+
sessionId: this.sessionId,
1267+
streamEventType: 'cloud.status',
1268+
payload: JSON.stringify({ cloudStatus: { type: 'error', message: failMessage } }),
1269+
timestamp: failNow,
12051270
});
12061271
}
12071272
}
@@ -2528,17 +2593,37 @@ export class CloudAgentSession extends DurableObject {
25282593
const orchestrator = this.getOrCreateOrchestrator();
25292594

25302595
const emitProgress = (step: string, message: string) => {
2596+
const now = Date.now();
25312597
this.broadcastVolatileEvent({
25322598
executionId,
25332599
sessionId,
25342600
streamEventType: 'preparing',
25352601
payload: JSON.stringify({ step, message }),
2536-
timestamp: Date.now(),
2602+
timestamp: now,
2603+
});
2604+
// cloud.status mirrors the preparation step
2605+
this.broadcastVolatileEvent({
2606+
executionId,
2607+
sessionId,
2608+
streamEventType: 'cloud.status',
2609+
payload: JSON.stringify({
2610+
cloudStatus: { type: 'preparing' as const, step, message },
2611+
}),
2612+
timestamp: now,
25372613
});
25382614
};
25392615

25402616
const result = await orchestrator.execute(plan, { onProgress: emitProgress });
25412617

2618+
// Emit cloud.status = ready after successful execution start
2619+
this.broadcastVolatileEvent({
2620+
executionId,
2621+
sessionId,
2622+
streamEventType: 'cloud.status',
2623+
payload: JSON.stringify({ cloudStatus: { type: 'ready' } }),
2624+
timestamp: Date.now(),
2625+
});
2626+
25422627
logger
25432628
.withFields({ sessionId, executionId, kiloSessionId: result.kiloSessionId })
25442629
.info('Execution started successfully');
@@ -2547,6 +2632,14 @@ export class CloudAgentSession extends DurableObject {
25472632
} catch (error) {
25482633
const errorMessage = error instanceof Error ? error.message : String(error);
25492634

2635+
this.broadcastVolatileEvent({
2636+
executionId,
2637+
sessionId,
2638+
streamEventType: 'cloud.status',
2639+
payload: JSON.stringify({ cloudStatus: { type: 'error', message: errorMessage } }),
2640+
timestamp: Date.now(),
2641+
});
2642+
25502643
await this.failExecution({
25512644
executionId,
25522645
status: 'failed',

0 commit comments

Comments
 (0)