Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/worker-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"./kilo-pass-bonus-projection": "./src/kilo-pass-bonus-projection.ts",
"./git-url": "./src/git-url.ts",
"./callback-token": "./src/callback-token.ts",
"./kilo-model-id": "./src/kilo-model-id.ts"
"./kilo-model-id": "./src/kilo-model-id.ts",
"./managed-session-policy": "./src/managed-session-policy.ts"
},
"scripts": {
"test": "vitest run",
Expand Down
59 changes: 59 additions & 0 deletions packages/worker-utils/src/cloud-agent-next-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ export type CallbackTarget = {
headers?: Record<string, string>;
};

export type CloudAgentRuntimeAgent = {
slug: string;
name: string;
config: Record<string, unknown>;
};

export type CloudAgentManagedSession = {
executionPolicy?: {
name: string;
permissionOverrides?: Record<string, unknown>;
tools?: Record<string, boolean>;
interaction?: {
question?: 'allow' | 'deny';
permission?: 'allow' | 'auto-approve' | 'auto-reject';
terminal?: 'allow' | 'deny';
};
runtime?: {
nonInteractive?: boolean;
};
};
};

export type CloudAgentMessageCompletion = {
callbackTarget?: CallbackTarget;
gateThreshold?: 'off' | 'all' | 'warning' | 'critical';
};

export type CloudAgentPrepareSessionInput = {
prompt: string;
mode: string;
Expand All @@ -30,10 +57,12 @@ export type CloudAgentPrepareSessionInput = {
kilocodeOrganizationId?: string;
envVars?: Record<string, string>;
mcpServers?: Record<string, unknown>;
runtimeAgents?: CloudAgentRuntimeAgent[];
upstreamBranch?: string;
callbackTarget?: CallbackTarget;
createdOnPlatform?: string;
gateThreshold?: 'off' | 'all' | 'warning' | 'critical';
managedSession?: CloudAgentManagedSession;
};

export type CloudAgentPrepareSessionOutput = {
Expand Down Expand Up @@ -66,6 +95,16 @@ export type CloudAgentSendMessageInput = {
gitToken?: string;
};

export type CloudAgentSendMessageInternalInput = Omit<
CloudAgentSendMessageInput,
'mode' | 'model' | 'variant'
> & {
mode?: string;
model?: string;
variant?: string;
completion?: CloudAgentMessageCompletion;
};

export type CloudAgentSendMessageOutput = {
executionId: string;
status?: string;
Expand Down Expand Up @@ -239,6 +278,11 @@ export type CloudAgentNextFetchClient = {
input: CloudAgentSendMessageInput
): Promise<CloudAgentSendMessageOutput>;

sendMessageV2Internal(
headers: Record<string, string>,
input: CloudAgentSendMessageInternalInput
): Promise<CloudAgentSendMessageOutput>;

getSessionHealth(
headers: Record<string, string>,
input: CloudAgentSessionHealthInput
Expand Down Expand Up @@ -310,6 +354,21 @@ export function createCloudAgentNextFetchClient(baseUrl: string): CloudAgentNext
return data as unknown as CloudAgentSendMessageOutput;
},

async sendMessageV2Internal(headers, input) {
const data = await trpcPost<Record<string, unknown>>(
trpc('sendMessageV2Internal'),
headers,
input,
'sendMessageV2Internal'
);
if (typeof data.executionId !== 'string') {
throw new Error(
`Unexpected sendMessageV2Internal response shape: ${JSON.stringify(data).slice(0, 500)}`
);
}
return data as unknown as CloudAgentSendMessageOutput;
},

async getSessionHealth(headers, input) {
const data = await trpcPost<Record<string, unknown>>(
trpc('getSessionHealth'),
Expand Down
11 changes: 11 additions & 0 deletions packages/worker-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ export type {
} from './cloud-agent-next-client.js';
export { CloudAgentNextBillingError, CloudAgentNextError } from './cloud-agent-next-client.js';

export {
CODE_REVIEW_RUNTIME_AGENT_SLUG,
buildCodeReviewManagedSessionPolicy,
buildCodeReviewRuntimeAgent,
} from './managed-session-policy.js';
export type {
CloudAgentRuntimeAgent,
ManagedSessionExecutionPolicy,
ManagedSessionInteractionPolicy,
} from './managed-session-policy.js';

export {
signKiloToken,
verifyKiloToken,
Expand Down
237 changes: 237 additions & 0 deletions packages/worker-utils/src/managed-session-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
export type ManagedSessionInteractionPolicy = {
question?: 'allow' | 'deny';
permission?: 'allow' | 'auto-approve' | 'auto-reject';
terminal?: 'allow' | 'deny';
};

export type ManagedSessionExecutionPolicy = {
name: string;
permissionOverrides?: Record<string, unknown>;
tools?: Record<string, boolean>;
interaction?: ManagedSessionInteractionPolicy;
runtime?: {
nonInteractive?: boolean;
};
};

export type CloudAgentRuntimeAgent = {
slug: string;
name: string;
config: {
prompt?: string;
description?: string;
mode?: 'subagent' | 'primary' | 'all';
model?: string | null;
variant?: string;
permission?: 'allow' | 'ask' | 'deny' | Record<string, unknown>;
options?: Record<string, unknown>;
};
};

export const CODE_REVIEW_RUNTIME_AGENT_SLUG = 'code-review';

const DEFAULT_DENIED_COMMAND_PATTERNS = ['rm -rf', 'sudo rm', 'mkfs', 'dd if='];

// mkdir and touch are intentionally allowed for agent scratch space during analysis.
const CODE_REVIEW_ALLOWED_COMMANDS = [
'ls',
'cat',
'echo',
'pwd',
'find',
'grep',
'wc',
'sort',
'uniq',
'cut',
'tr',
'nl',
'jq',
'git',
'git fetch',
'git pull',
'gh pr diff',
'gh pr view',
'gh api repos/*/issues/*/comments --input*',
'gh api repos/*/issues/comments/* -X PATCH*',
'gh api repos/*/pulls/*/reviews --input*',
'glab mr diff',
'glab mr view',
'glab api --method POST *merge_requests/*/notes*',
'glab api --method PUT *merge_requests/*/notes/*',
'glab api --method POST *merge_requests/*/discussions*',
'whoami',
'date',
'stat',
'file',
'head',
'tail',
'sed',
'cd',
'mkdir',
'touch',
];

const CODE_REVIEW_DENIED_COMMAND_PATTERNS = [
'bash',
'sh',
'zsh',
'fish',
'sed -i',
'sed -*i',
'sed --in-place',
'sed --in-place*',
'sed * -i',
'sed * -*i',
'sed * --in-place',
'sed * --in-place*',
'sort -o',
'sort -o*',
'sort -*o',
'sort --output',
'sort --output*',
'sort * -o',
'sort * -o*',
'sort * -*o',
'sort * --output',
'sort * --output*',
'uniq * *',
'python',
'python3',
'node',
'irb',
'php -a',
'rails console',
'vi',
'vim',
'nvim',
'nano',
'emacs',
'less',
'more',
'top',
'htop',
'watch',
'tail -f',
'ssh',
'tmux',
'screen',
'git add',
'git branch',
'git clean',
'git commit',
'git config',
'git mv',
'git push',
'git restore',
'git rm',
'git merge',
'git rebase',
'git cherry-pick',
'git reset',
'git checkout',
'git switch',
'git stash',
'git tag',
'git worktree',
'git am',
'git apply',
'git remote set-url',
'gh pr merge',
'gh pr review',
'gh pr create',
'gh pr close',
'gh pr edit',
'gh pr checkout',
'gh auth login',
'gh auth refresh',
'gh issue',
'gh repo create',
'gh repo fork',
'glab auth',
'glab mr approve',
'glab mr close',
'glab mr create',
'glab mr delete',
'glab mr merge',
'glab mr reopen',
'glab mr update',
'glab repo',
'glab issue',
'glab pipeline',
'glab release',
'glab variable',
'npm test',
'pnpm test',
'bun test',
'yarn test',
'pytest',
'vitest',
];

function buildCommandGuardBashPermissions(params: {
allowed: readonly string[];
denied: readonly string[];
}): Record<string, string> {
const bashPermissions: Record<string, string> = {};
for (const cmd of params.allowed) {
bashPermissions[cmd] = 'allow';
bashPermissions[`${cmd} *`] = 'allow';
}
for (const cmd of params.denied) {
bashPermissions[cmd] = 'deny';
bashPermissions[`${cmd} *`] = 'deny';
}
return bashPermissions;
}

export function buildCodeReviewManagedSessionPolicy(): ManagedSessionExecutionPolicy {
return {
name: 'code-review-read-only',
runtime: { nonInteractive: true },
interaction: {
question: 'deny',
permission: 'auto-reject',
terminal: 'deny',
},
tools: {
question: false,
plan_enter: false,
plan_exit: false,
},
permissionOverrides: {
read: 'allow',
edit: 'deny',
bash: buildCommandGuardBashPermissions({
allowed: CODE_REVIEW_ALLOWED_COMMANDS,
denied: [...DEFAULT_DENIED_COMMAND_PATTERNS, ...CODE_REVIEW_DENIED_COMMAND_PATTERNS],
}),
webfetch: 'deny',
websearch: 'deny',
codesearch: 'deny',
todowrite: 'allow',
todoread: 'allow',
question: 'deny',
},
};
}

export function buildCodeReviewRuntimeAgent(options: {
model: string;
variant?: string;
permission?: Record<string, unknown>;
}): CloudAgentRuntimeAgent {
return {
slug: CODE_REVIEW_RUNTIME_AGENT_SLUG,
name: 'Code Review',
config: {
mode: 'primary',
model: options.model,
variant: options.variant,
description: 'Read-only, non-interactive code review agent.',
prompt:
'You are Kilo Code performing a read-only pull request review. Inspect the repository and diff, do not modify files, and follow the user prompt for review output.',
...(options.permission ? { permission: options.permission } : {}),
},
};
}
6 changes: 6 additions & 0 deletions services/cloud-agent-next/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ This pattern blocks API endpoints from running for external contributors who don
- Put Kilo/job behavior in `wrapper/` or Kilo SDK integration code when it does not require durable DO coordination.
- Avoid growing `CloudAgentSession.ts` with product behavior that can live in the wrapper, Kilo SDK layer, or a small helper module.

### Code Review Integration Boundary

- `services/code-review-infra/src/code-review-orchestrator.ts` owns code-review product and workflow semantics: review IDs and attempt state, status reconciliation, retry or fresh-session selection, review callback construction, cancellation policy, and review-specific dispatch rules.
- `CloudAgentSession` may provide reusable durable runtime operations used by code review, including the retained `prepareSession` / `updateSession` / `sendMessageV2` callback seam and runtime enforcement of the read-only code-review command guard. It must not acquire review IDs or attempt state, review status reconciliation, retry or fresh-session selection, review callback construction, or review-specific dispatch rules.
- When code-review integration needs another generic session or runtime capability, expose a narrow reusable primitive here and leave the code-review decision and policy in `CodeReviewOrchestrator`.

### Runtime Guidelines

- Durable Object calls should be retried using `withDORetry` in `src/utils/do-retry.ts`
Expand Down
1 change: 1 addition & 0 deletions services/cloud-agent-next/src/balance-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export async function fetchOrgIdForSession(
export const BALANCE_REQUIRED_MUTATIONS = new Set([
'initiateFromKilocodeSessionV2',
'sendMessageV2',
'sendMessageV2Internal',
'start',
'send',
]);
Loading