Skip to content
Merged
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
15 changes: 15 additions & 0 deletions .changeset/session-goals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@kilocode/cli": minor
"kilo-code": minor
"@kilocode/kilo-ui": patch
---

Keep working toward a session goal with `/goal`, with shared pause, resume, and clear controls in the terminal and VS Code. Pause goals after no-action replies, terminal failures, Stop, new messages, and backend restarts. Rename custom commands or MCP prompts named `goal` to use this reserved command. Show a labeled Goal icon with hover details while work runs.

Compose multiline goals with images and file attachments in VS Code. Select `/goal` to enter goal mode, or cancel to keep the draft as ordinary chat. Keep drafts and attachments when submission fails.

Keep the current goal running when replacement attachments are invalid. Make pending Goal submissions read-only, and preserve the draft when Cancel exits Goal mode before acknowledgement.

Disable clarification questions during active goals and delegated work while keeping permission approvals unchanged. Make safe, reversible decisions autonomously and report completion or blockers.

Retain Active, Complete, Blocked, and Paused goals with their objective and reason until explicitly cleared. Let the working model explicitly report completion or a blocker with the Goal-only reporting tool, without a separate evaluator or independent verification claim. Pause no-action turns that have no explicit report. Keep complete goals complete after a backend restart and label their resume action as Restart.
18 changes: 9 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/kilo-ui/src/components/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ const icons: Record<string, { path: string; viewBox: string }> = {
viewBox: "0 0 24 24",
path: `<path d="M12 14L9 10M12 14L15 10M21 15C21 18.866 17.866 22 14 22H10C6.134 22 3 18.866 3 15V9C3 5.134 6.134 2 10 2H14C17.866 2 21 5.134 21 9V15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`,
},
target: {
viewBox: "0 0 20 20",
path: `<circle cx="10" cy="10" r="7.5" stroke="currentColor"/><circle cx="10" cy="10" r="4.5" stroke="currentColor"/><circle cx="10" cy="10" r="1.5" fill="currentColor"/>`,
},
local: {
viewBox: "0 0 20 20",
path: `<rect x="2.5" y="3.5" width="15" height="10" rx="1" stroke="currentColor"/><path d="M6 16.5H14" stroke="currentColor" stroke-linecap="square"/><path d="M10 13.5V16.5" stroke="currentColor"/>`,
Expand Down
50 changes: 29 additions & 21 deletions packages/kilo-vscode/src/KiloProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ import { nativeTitle } from "./kilo-provider/native-tab-title"
import { isActivity, type Activity } from "../webview-ui/src/utils/session-activity"
import type { PRReviewCommentData, ReviewMessageData } from "./shared/review-comments"
import { feedbackMetadata, parseFeedback, type BrowserFeedbackData } from "./shared/browser-feedback"
import { completesWithoutStatus } from "./kilo-provider/command-completion"
import { completesWithoutStatus, goalControl } from "./kilo-provider/command-completion"
import { KiloProviderMemory } from "./kilo-provider/memory"

import {
Expand Down Expand Up @@ -4278,7 +4278,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
)
resolved = await this.resolveSession(sessionID, draftID, context, contextDirectory)
if (!resolved) return
if (sandbox) await sandbox
const control = goalControl(command, args)
const stopping = control && args.trim() !== ""
if (sandbox && !stopping) await sandbox
const sid = resolved.sid
const dir = resolved.dir

Expand All @@ -4294,26 +4296,31 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
source: f.source,
}))

await this.checkpoints.get(sid)
await runWithMessageConfirmation(this.confirmations, messageID, "KiloProvider: Command request", () =>
this.withRetry(
() =>
this.client!.session.command({
sessionID: sid,
directory: dir,
command,
arguments: args,
messageID,
model: providerID && modelID ? `${providerID}/${modelID}` : undefined,
agent,
variant,
parts,
snapshotInitialization: this.opts.snapshotInitialization,
}),
sid,
if (!control) await this.checkpoints.get(sid)
const send = () =>
this.client!.session.command({
sessionID: sid,
directory: dir,
command,
arguments: args,
messageID,
),
)
model: !control && providerID && modelID ? `${providerID}/${modelID}` : undefined,
agent: control ? undefined : agent,
variant: control ? undefined : variant,
parts,
snapshotInitialization: this.opts.snapshotInitialization,
})
await runWithMessageConfirmation(this.confirmations, messageID, "KiloProvider: Command request", async () => {
if (command !== "goal") return this.withRetry(send, sid, messageID)
const result = await send()
if (result.error) throw result.error
if (args.trim()) return
const message = result.data?.parts
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n")
if (message) void vscode.window.showInformationMessage(message)
})
if (messageID && completesWithoutStatus(command)) {
this.postMessage({ type: "sessionCommandCompleted", messageID })
}
Expand Down Expand Up @@ -4528,6 +4535,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
trackedSessionIds: this.trackedSessionIds,
connectionService: this.connectionService,
postMessage: (msg) => this.postMessage(msg),
notify: (message) => void vscode.window.showInformationMessage(message),
getWorkspaceDirectory: (sid) => this.getWorkspaceDirectory(sid),
gatherEditorContext: () => this.gatherEditorContext(),
runWithMessageConfirmation: (id, label, run) => runWithMessageConfirmation(this.confirmations, id, label, run),
Expand Down
25 changes: 24 additions & 1 deletion packages/kilo-vscode/src/kilo-provider-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ export async function runWithMessageConfirmation<T>(
}
}

export function sessionToWebview(session: Pick<Session, "id" | "parentID" | "title" | "time" | "summary" | "revert">) {
export function sessionToWebview(
session: Pick<Session, "id" | "parentID" | "title" | "time" | "summary" | "revert" | "metadata">,
) {
const goal = session.metadata?.["kilo.goal"]
return {
id: session.id,
parentID: session.parentID ?? null,
Expand All @@ -206,6 +209,26 @@ export function sessionToWebview(session: Pick<Session, "id" | "parentID" | "tit
// SolidJS store merge never clears the existing revert state.
revert: session.revert ?? null,
summary: session.summary ?? null,
goal:
goal &&
typeof goal === "object" &&
"text" in goal &&
typeof goal.text === "string" &&
"active" in goal &&
typeof goal.active === "boolean"
? {
text: goal.text,
active: goal.active,
...("status" in goal &&
(goal.status === "active" ||
goal.status === "complete" ||
goal.status === "blocked" ||
goal.status === "paused")
? { status: goal.status }
: {}),
...("reason" in goal && typeof goal.reason === "string" ? { reason: goal.reason } : {}),
}
: null,
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/kilo-vscode/src/kilo-provider/command-completion.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export function completesWithoutStatus(command: string): boolean {
return command === "local-review" || command === "local-review-uncommitted"
return command === "goal" || command === "local-review" || command === "local-review-uncommitted"
}

export function goalControl(command: string, args: string): boolean {
return command === "goal" && ["", "pause", "clear"].includes(args.trim())
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface CloudSessionContext {
recordMessageSessionId(messageId: string, sessionId: string): void
}
postMessage(msg: unknown): void
notify?(message: string): void
getWorkspaceDirectory(sessionId?: string): string
gatherEditorContext(): Promise<EditorContext>
runWithMessageConfirmation?<T>(
Expand Down Expand Up @@ -195,7 +196,7 @@ export async function handleImportAndSend(
filename: f.filename,
source: f.source,
}))
await client.session.command(
const result = await client.session.command(
{
sessionID: session.id,
directory: dir,
Expand All @@ -209,6 +210,13 @@ export async function handleImportAndSend(
},
{ throwOnError: true },
)
if (command === "goal" && !commandArgs?.trim()) {
const message = result.data.parts
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n")
if (message) ctx.notify?.(message)
}
return
}

Expand Down
16 changes: 13 additions & 3 deletions packages/kilo-vscode/src/services/attention/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function previewSound(value: string) {

export class AttentionService implements vscode.Disposable {
private readonly active = new Set<string>()
private readonly goals = new Set<string>()
private readonly errored = new Set<string>()
private readonly questions = new Set<string>()
private readonly permissions = new Set<string>()
Expand Down Expand Up @@ -59,12 +60,20 @@ export class AttentionService implements vscode.Disposable {
}

private sync(event: Sync) {
if (event.name !== "session.deleted.1") return
this.remove(event.data.sessionID)
if (event.name === "session.deleted.1") return this.remove(event.data.sessionID)
if (event.name !== "session.updated.1" && event.name !== "session.created.1") return
if (!("metadata" in event.data.info)) return
const goal = event.data.info.metadata?.["kilo.goal"]
if (goal && typeof goal === "object" && "active" in goal && goal.active === true) {
this.goals.add(event.data.sessionID)
return
}
this.goals.delete(event.data.sessionID)
}

private remove(sessionID: string) {
this.active.delete(sessionID)
this.goals.delete(sessionID)
this.errored.delete(sessionID)
}

Expand Down Expand Up @@ -110,7 +119,7 @@ export class AttentionService implements vscode.Disposable {
if (!this.active.delete(sessionID)) return
if (this.errored.delete(sessionID)) return
if (event.properties.reason !== "completed") return
if (event.properties.parentID !== undefined) return
if (event.properties.parentID !== undefined || this.goals.has(sessionID)) return
this.notify("done")
}

Expand All @@ -131,6 +140,7 @@ export class AttentionService implements vscode.Disposable {

private reset() {
this.active.clear()
this.goals.clear()
this.errored.clear()
this.questions.clear()
this.permissions.clear()
Expand Down
Loading
Loading