Skip to content

Commit 602484a

Browse files
authored
Chronicle - local and remote (#308602)
* chronicle * local and cloud store * upload vscode events to remote store * few updates * consent ui and settings * few optimizations * test fix * feedback updates * test fix * check setting to enable cmd * fix test * Settings update and tool update * command update * Settings update * merge main * feedback updates * setting and test update * few updates * updates * test update * blocks ci update * feedback updates * comment update
1 parent 503edcb commit 602484a

37 files changed

+4955
-5
lines changed

extensions/copilot/package.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,32 @@
11741174
"legacyToolReferenceFullNames": [
11751175
"editFiles"
11761176
]
1177+
},
1178+
{
1179+
"name": "copilot_sessionStoreSql",
1180+
"displayName": "Session Store SQL",
1181+
"toolReferenceName": "sessionStoreSql",
1182+
"userDescription": "Query your Copilot session history using SQL",
1183+
"modelDescription": "Execute read-only SQL queries against the global session store containing history from ALL past coding sessions. Use this proactively when the user asks about:\n- What they've worked on recently or in the past\n- Prior approaches to similar problems\n- Project history and file changes\n- Sessions linked to PRs, issues, or commits\n- Temporal queries ('what was I doing yesterday?')\n\nSupports SQLite SQL including JOINs, FTS5 MATCH queries, aggregations, and subqueries.\n\n**Only one query per call — do not combine multiple statements with semicolons.**\n\nSchema:\n- sessions — id, cwd, repository, branch, summary, created_at, updated_at\n- turns — session_id, turn_index, user_message, assistant_response, timestamp\n- session_files — session_id, file_path, tool_name (edit/create), turn_index\n- session_refs — session_id, ref_type (commit/pr/issue), ref_value, turn_index\n- search_index — FTS5 virtual table (content, session_id, source_type). Use WHERE search_index MATCH 'query' for full-text search.",
1184+
"tags": [],
1185+
"canBeReferencedInPrompt": false,
1186+
"inputSchema": {
1187+
"type": "object",
1188+
"properties": {
1189+
"query": {
1190+
"type": "string",
1191+
"description": "A single read-only SQL query to execute. Supports SELECT, WITH, JOINs, aggregations, and FTS5 MATCH. Only one statement per call — do not combine multiple queries with semicolons."
1192+
},
1193+
"description": {
1194+
"type": "string",
1195+
"description": "A 2-5 word summary of what this query does (e.g. 'Recent sessions overview', 'Find PR sessions')."
1196+
}
1197+
},
1198+
"required": [
1199+
"query",
1200+
"description"
1201+
]
1202+
}
11771203
}
11781204
],
11791205
"languageModelToolSets": [
@@ -1487,6 +1513,21 @@
14871513
"name": "compact",
14881514
"description": "%copilot.agent.compact.description%"
14891515
},
1516+
{
1517+
"name": "chronicle",
1518+
"description": "%copilot.chronicle.description%",
1519+
"when": "github.copilot.sessionSearch.enabled"
1520+
},
1521+
{
1522+
"name": "chronicle:standup",
1523+
"description": "%copilot.chronicle.standup.description%",
1524+
"when": "github.copilot.sessionSearch.enabled"
1525+
},
1526+
{
1527+
"name": "chronicle:tips",
1528+
"description": "%copilot.chronicle.tips.description%",
1529+
"when": "github.copilot.sessionSearch.enabled"
1530+
},
14901531
{
14911532
"name": "explain",
14921533
"description": "%copilot.workspace.explain.description%"

extensions/copilot/package.nls.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@
168168
"copilot.edits.description": "Edit files in your workspace",
169169
"copilot.agent.description": "Edit files in your workspace in agent mode",
170170
"copilot.agent.compact.description": "Free up context by compacting the conversation history. Optionally include extra instructions for compaction.",
171+
"copilot.chronicle.description": "Session history tools and insights",
172+
"copilot.chronicle.standup.description": "Generate a standup report from recent coding sessions",
173+
"copilot.chronicle.tips.description": "Get personalized tips based on your Copilot usage patterns",
174+
"github.copilot.config.sessionSearch.enabled": "Enable session search and /chronicle commands. This is a team-internal setting.",
175+
"github.copilot.config.sessionSearch.localIndex.enabled": "Enable local session tracking. When enabled, Copilot tracks session data locally for /chronicle commands.",
176+
"github.copilot.config.sessionSearch.cloudSync.enabled": "Enable cloud sync for session data. When enabled, session data is synced to your Copilot account for cross-device access.",
177+
"github.copilot.config.sessionSearch.cloudSync.excludeRepositories": "Repository patterns to exclude from cloud sync. Use exact `owner/repo` names or glob patterns like `my-org/*`. Sessions from matching repos will only be stored locally.",
171178
"copilot.workspace.explain.description": "Explain how the code in your active editor works",
172179
"copilot.workspace.edit.description": "Edit files in your workspace",
173180
"copilot.workspace.review.description": "Review the selected code in your active editor",
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
/**
7+
* Circuit breaker states:
8+
* - CLOSED: Normal operation, requests are allowed
9+
* - OPEN: Circuit is tripped, requests fail immediately
10+
* - HALF_OPEN: Testing if the service has recovered
11+
*/
12+
export const CircuitState = {
13+
CLOSED: 'CLOSED',
14+
OPEN: 'OPEN',
15+
HALF_OPEN: 'HALF_OPEN',
16+
} as const;
17+
18+
export type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
19+
20+
export interface CircuitBreakerOptions {
21+
/** Number of consecutive failures before opening the circuit. */
22+
failureThreshold: number;
23+
/** Time in milliseconds before attempting to close the circuit. */
24+
resetTimeoutMs: number;
25+
/** Time in milliseconds before a probe request times out and allows another probe. */
26+
probeTimeoutMs: number;
27+
/** Maximum reset timeout after exponential backoff on failed probes. Defaults to resetTimeoutMs (no backoff). */
28+
maxResetTimeoutMs?: number;
29+
}
30+
31+
const DEFAULT_OPTIONS: CircuitBreakerOptions = {
32+
failureThreshold: 5,
33+
resetTimeoutMs: 1_000,
34+
probeTimeoutMs: 30_000,
35+
};
36+
37+
/**
38+
* Circuit breaker implementation to prevent cascading failures.
39+
*
40+
* When multiple consecutive failures occur, the circuit "opens" and
41+
* immediately rejects subsequent requests without attempting them.
42+
* After a cooldown period, it allows a single "probe" request to test
43+
* if the service has recovered.
44+
*
45+
* Ported from copilot-agent-runtime/src/helpers/circuit-breaker.ts.
46+
*/
47+
export class CircuitBreaker {
48+
private state: CircuitState = CircuitState.CLOSED;
49+
private failureCount: number = 0;
50+
private lastFailureTime: number = 0;
51+
private probeInFlight: boolean = false;
52+
private probeStartTime: number = 0;
53+
private readonly options: CircuitBreakerOptions;
54+
/** Current reset timeout, increases via exponential backoff on failed probes. */
55+
private currentResetTimeoutMs: number;
56+
57+
constructor(options: Partial<CircuitBreakerOptions> = {}) {
58+
this.options = { ...DEFAULT_OPTIONS, ...options };
59+
this.currentResetTimeoutMs = this.options.resetTimeoutMs;
60+
}
61+
62+
/**
63+
* Get the current state of the circuit breaker.
64+
*/
65+
getState(): CircuitState {
66+
this.updateState();
67+
return this.state;
68+
}
69+
70+
/**
71+
* Check if requests should be allowed through.
72+
* In HALF_OPEN state, only one probe request is allowed at a time.
73+
*/
74+
canRequest(): boolean {
75+
this.updateState();
76+
77+
switch (this.state) {
78+
case CircuitState.CLOSED:
79+
return true;
80+
case CircuitState.HALF_OPEN:
81+
// Check if probe has timed out — prevents permanent deadlock
82+
if (this.probeInFlight) {
83+
const probeElapsed = Date.now() - this.probeStartTime;
84+
if (probeElapsed >= this.options.probeTimeoutMs) {
85+
this.probeInFlight = false;
86+
}
87+
}
88+
if (this.probeInFlight) {
89+
return false;
90+
}
91+
this.probeInFlight = true;
92+
this.probeStartTime = Date.now();
93+
return true;
94+
case CircuitState.OPEN:
95+
return false;
96+
}
97+
}
98+
99+
/**
100+
* Record a successful request. Resets failure count and closes the circuit.
101+
*/
102+
recordSuccess(): void {
103+
this.failureCount = 0;
104+
this.probeInFlight = false;
105+
this.currentResetTimeoutMs = this.options.resetTimeoutMs;
106+
this.state = CircuitState.CLOSED;
107+
}
108+
109+
/**
110+
* Record a failed request. May open the circuit if threshold is exceeded.
111+
*/
112+
recordFailure(): void {
113+
const wasHalfOpen = this.state === CircuitState.HALF_OPEN;
114+
this.failureCount++;
115+
this.lastFailureTime = Date.now();
116+
this.probeInFlight = false;
117+
118+
if (this.failureCount >= this.options.failureThreshold) {
119+
this.state = CircuitState.OPEN;
120+
}
121+
122+
// Exponential backoff: double the probe interval after each failed probe
123+
if (wasHalfOpen && this.state === CircuitState.OPEN) {
124+
const maxTimeout = this.options.maxResetTimeoutMs ?? this.options.resetTimeoutMs;
125+
this.currentResetTimeoutMs = Math.min(this.currentResetTimeoutMs * 2, maxTimeout);
126+
}
127+
}
128+
129+
/**
130+
* Get the number of consecutive failures.
131+
*/
132+
getFailureCount(): number {
133+
return this.failureCount;
134+
}
135+
136+
/**
137+
* Force reset the circuit breaker to closed state.
138+
*/
139+
reset(): void {
140+
this.state = CircuitState.CLOSED;
141+
this.failureCount = 0;
142+
this.lastFailureTime = 0;
143+
this.probeInFlight = false;
144+
this.probeStartTime = 0;
145+
this.currentResetTimeoutMs = this.options.resetTimeoutMs;
146+
}
147+
148+
/**
149+
* Update state based on timeout — transitions from OPEN to HALF_OPEN
150+
* after the reset timeout has elapsed.
151+
*/
152+
private updateState(): void {
153+
if (this.state === CircuitState.OPEN) {
154+
const elapsed = Date.now() - this.lastFailureTime;
155+
if (elapsed >= this.currentResetTimeoutMs) {
156+
this.state = CircuitState.HALF_OPEN;
157+
}
158+
}
159+
}
160+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
// ── Cloud session API types ─────────────────────────────────────────────────────
7+
8+
/**
9+
* Resolved GitHub repository numeric IDs (from GitHub REST API).
10+
*/
11+
export interface RepoIdentifiers {
12+
ownerId: number;
13+
repoId: number;
14+
}
15+
16+
/**
17+
* GitHub repository context combining string names with resolved numeric IDs.
18+
*/
19+
export interface GitHubRepository {
20+
owner: string;
21+
repo: string;
22+
repoIds: RepoIdentifiers;
23+
}
24+
25+
/**
26+
* Cloud session and task IDs for an active remote session.
27+
*/
28+
export interface CloudSessionIds {
29+
cloudSessionId: string;
30+
cloudTaskId: string;
31+
}
32+
33+
/**
34+
* Response from creating a cloud session.
35+
*/
36+
export interface CreateSessionResponse {
37+
id: string;
38+
task_id?: string;
39+
agent_task_id?: string;
40+
}
41+
42+
/**
43+
* A cloud session.
44+
*/
45+
export interface CloudSession {
46+
id: string;
47+
state: string;
48+
task_id?: string;
49+
owner_id: number;
50+
repo_id: number;
51+
created_at: string;
52+
updated_at: string;
53+
}
54+
55+
// ── Session event types (CLI-compatible) ────────────────────────────────────────
56+
// Event format compatible with the cloud session pipeline.
57+
58+
/**
59+
* Base structure for all session events sent to the cloud.
60+
*/
61+
export interface SessionEvent {
62+
/** Unique event identifier (UUID v4). */
63+
id: string;
64+
/** ISO 8601 timestamp when the event was created. */
65+
timestamp: string;
66+
/** ID of the chronologically preceding event, forming a linked chain. Null for the first event. */
67+
parentId: string | null;
68+
/** When true, the event is transient and not persisted. */
69+
ephemeral?: boolean;
70+
/** Event type discriminator. */
71+
type: string;
72+
/** Event-specific payload. */
73+
data: Record<string, unknown>;
74+
}
75+
76+
/**
77+
* Working directory context schema for session.start events.
78+
*/
79+
export interface WorkingDirectoryContext {
80+
cwd?: string;
81+
repository?: string;
82+
branch?: string;
83+
headCommit?: string;
84+
}
85+
86+
/** Reason why session creation failed. */
87+
export type CreateSessionFailureReason = 'policy_blocked' | 'error';
88+
89+
/** Result of attempting to create a cloud session. */
90+
export type CreateSessionResult =
91+
| { ok: true; response: CreateSessionResponse }
92+
| { ok: false; reason: CreateSessionFailureReason };

0 commit comments

Comments
 (0)