Skip to content

Commit 9c9736b

Browse files
authored
Add basic log streaming (#7112)
1 parent d818e52 commit 9c9736b

8 files changed

Lines changed: 86 additions & 31 deletions

File tree

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,7 +1661,7 @@
16611661
},
16621662
{
16631663
"command": "codingAgent.openSessionLog",
1664-
"title": "Open Coding Agent Session Log",
1664+
"title": "%command.codingAgent.openSessionLog.title%",
16651665
"category": "%command.pull.request.category%"
16661666
}
16671667
],
@@ -3967,7 +3967,6 @@
39673967
"jsdom": "19.0.0",
39683968
"jsdom-global": "3.0.2",
39693969
"json5": "2.2.2",
3970-
"markdown-it": "^14.1.0",
39713970
"merge-options": "3.0.4",
39723971
"minimist": "^1.2.6",
39733972
"mkdirp": "1.0.4",
@@ -3981,7 +3980,6 @@
39813980
"process": "^0.11.10",
39823981
"raw-loader": "4.0.2",
39833982
"react-testing-library": "7.0.1",
3984-
"shiki": "^3.7.0",
39853983
"sinon": "9.0.0",
39863984
"source-map-support": "0.5.19",
39873985
"stream-browserify": "^3.0.0",
@@ -4015,11 +4013,14 @@
40154013
"jszip": "^3.10.1",
40164014
"lru-cache": "6.0.0",
40174015
"marked": "^4.0.10",
4016+
"markdown-it": "^14.1.0",
40184017
"monaco-editor": "^0.52.2",
40194018
"react": "^16.12.0",
40204019
"react-dom": "^16.12.0",
4020+
"shiki": "^3.7.0",
40214021
"ssh-config": "4.1.1",
40224022
"stream-http": "^3.2.0",
4023+
"temporal-polyfill": "^0.3.0",
40234024
"tunnel": "0.0.6",
40244025
"url-search-params-polyfill": "^8.1.1",
40254026
"uuid": "8.3.2",

package.nls.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
"githubPullRequests.defaultCreateOption.createAutoMerge": "The pull request will be created with auto-merge enabled. The merge method selected will be the default for the repo or the value of `githubPullRequests.defaultMergeMethod` if set.",
1414
"githubPullRequests.createDraft": "Whether the \"Draft\" checkbox will be checked by default when creating a pull request.",
1515
"githubPullRequests.logLevel.description": "Logging for GitHub Pull Request extension. The log is emitted to the output channel named as GitHub Pull Request.",
16-
"githubPullRequests.logLevel.markdownDeprecationMessage":{
16+
"githubPullRequests.logLevel.markdownDeprecationMessage": {
1717
"message": "Log level is now controlled by the [Developer: Set Log Level...](command:workbench.action.setLogLevel) command. You can set the log level for the current session and also the default log level from there.",
18-
"comment" : [
18+
"comment": [
1919
"Do not translate what's inside of (...). It is link syntax.",
2020
"{Locked='](command:workbench.action.setLogLevel)'}"
2121
]
@@ -153,7 +153,7 @@
153153
"view.github.login.name": "Login",
154154
"view.pr.github.name": "Pull Requests",
155155
"view.pr.github.accessibilityHelpContent": {
156-
"message":"Helpful commands include:\n-GitHub Pull Requests: Refresh Pull Requests List<keybinding:pr.refreshList>\n-GitHub Pull Requests: Focus on Issues View<keybinding:issues:github.focus> \n-GitHub Pull Requests: Focus on Pull Requests View<keybinding:pr:github.focus>\n-GitHub Issues: Copy GitHub Permalink<keybinding:issue.copyGithubPermalink>\n-GitHub Issues: Create an Issue<keybinding:issue.createIssueFromFile>\n-GitHub Pull Requests: Create Pull Request<keybinding:pr.create>",
156+
"message": "Helpful commands include:\n-GitHub Pull Requests: Refresh Pull Requests List<keybinding:pr.refreshList>\n-GitHub Pull Requests: Focus on Issues View<keybinding:issues:github.focus> \n-GitHub Pull Requests: Focus on Pull Requests View<keybinding:pr:github.focus>\n-GitHub Issues: Copy GitHub Permalink<keybinding:issue.copyGithubPermalink>\n-GitHub Issues: Create an Issue<keybinding:issue.createIssueFromFile>\n-GitHub Pull Requests: Create Pull Request<keybinding:pr.create>",
157157
"comment": [
158158
"Do not translate the contents of (...) or <...> in the message. They are commands that will be replaced with the actual command name and keybinding."
159159
]
@@ -307,17 +307,18 @@
307307
"command.notifications.markPullRequestsAsDone.title": "Mark Pull Requests as Done",
308308
"command.notifications.configureNotificationsViewlet.title": "Configure...",
309309
"command.notification.chatSummarizeNotification.title": "Summarize With Copilot",
310+
"command.codingAgent.openSessionLog.title": "Open Coding Agent Session Log",
310311
"welcome.github.login.contents": {
311312
"message": "You have not yet signed in with GitHub\n[Sign in](command:pr.signin)",
312-
"comment" : [
313+
"comment": [
313314
"Do not translate what's inside of (...). It is link syntax.",
314315
"{Locked='](command:pr.signin)'}"
315316
]
316317
},
317318
"welcome.github.noGit.contents": "Git is not installed or otherwise not available. Install git or fix your git installation and then reload.",
318319
"welcome.github.loginNoEnterprise.contents": {
319320
"message": "You have not yet signed in with GitHub\n[Sign in](command:pr.signinNoEnterprise)",
320-
"comment" : [
321+
"comment": [
321322
"Do not translate what's inside of (...). It is link syntax.",
322323
"{Locked='](command:pr.signinNoEnterprise)'}"
323324
]
@@ -332,7 +333,7 @@
332333
"welcome.pr.github.uninitialized.contents": "Loading...",
333334
"welcome.pr.github.noFolder.contents": {
334335
"message": "You have not yet opened a folder.\n[Open Folder](command:workbench.action.files.openFolder)",
335-
"comment" : [
336+
"comment": [
336337
"Do not translate what's inside of (...). It is link syntax.",
337338
"{Locked='](command:workbench.action.files.openFolder)'}"
338339
]
@@ -374,4 +375,4 @@
374375
"languageModelTools.github-pull-request_activePullRequest.description": "Get information about the active GitHub pull request. This information includes: comments, files changed, pull request title + description, pull request state, and pull request status checks/CI.",
375376
"languageModelTools.github-pull-request_copilot-coding-agent.displayName": "Copilot Coding Agent",
376377
"languageModelTools.github-pull-request_copilot-coding-agent.userDescription": "Completes the provided task using an asynchronous coding agent. Use when the user wants copilot continue completing a task in the background or asynchronously. Launch an autonomous GitHub Copilot agent to work on coding tasks in the background. The agent will create a new branch, implement the requested changes, and open a pull request with the completed work."
377-
}
378+
}

src/view/sessionLogView.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -313,26 +313,32 @@ class SessionLogView extends Disposable {
313313
this.webviewPanel.webview.postMessage({
314314
type: 'loaded',
315315
info,
316-
logs,
316+
logs
317317
} as messages.LoadedMessage);
318318

319319
if (info.state === 'in_progress') {
320-
// Poll for updates every 5 seconds
320+
// Poll for updates
321321
const interval = setInterval(async () => {
322322
if (this._isDisposed) {
323323
clearInterval(interval);
324324
return;
325325
}
326326

327-
const updatedInfo = await this.copilotApi.getSessionInfo(this.sessionId);
328-
if (updatedInfo.state !== info.state) {
329-
this.webviewPanel.webview.postMessage({
330-
type: 'loaded',
331-
info: updatedInfo,
332-
logs,
333-
} as messages.LoadedMessage);
327+
const [newInfo, newLogs] = await Promise.all([
328+
this.copilotApi.getSessionInfo(this.sessionId),
329+
this.copilotApi.getLogsFromSession(this.sessionId)
330+
]);
331+
332+
this.webviewPanel.webview.postMessage({
333+
type: 'update',
334+
info: newInfo,
335+
logs: newLogs
336+
} as messages.UpdateMessage);
337+
338+
if (newInfo.state !== 'in_progress') {
339+
clearInterval(interval);
334340
}
335-
}, 5000);
341+
}, 3000);
336342

337343
this._register({
338344
dispose: () => clearInterval(interval)

webviews/sessionLogView/app.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function App() {
2525
React.useEffect(() => {
2626
let themeP: Promise<void> | undefined;
2727
const handleMessage = async (event: MessageEvent) => {
28-
const message = event.data as messages.InitMessage | messages.ChangeThemeMessage | messages.LoadedMessage;
28+
const message = event.data as messages.InitMessage | messages.ChangeThemeMessage | messages.LoadedMessage | messages.UpdateMessage;
2929
switch (message?.type) {
3030
case 'init': {
3131
themeP = registerMonacoTheme(message.themeData);
@@ -37,7 +37,9 @@ export function App() {
3737
setPullInfo(message.pullInfo);
3838
break;
3939
}
40-
case 'loaded': {
40+
41+
case 'loaded':
42+
case 'update': {
4143
await themeP;
4244
setState({
4345
state: 'ready',

webviews/sessionLogView/index.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@
1414
position: relative;
1515
max-width: 800px;
1616
margin: 0 auto;
17-
padding-top: 1em;
17+
padding: 1em 0;
18+
}
19+
20+
.session-in-progress-indicator {
21+
margin-top: 1em;
22+
text-align: center;
23+
color: var(--vscode-descriptionForeground);
24+
25+
.codicon {
26+
margin-right: 0.5em;
27+
transform: rotate(0);
28+
animation: spin 1s linear infinite;
29+
}
1830
}
1931

2032
.session-header-info {
@@ -157,4 +169,10 @@ button.codeview-expand:hover {
157169
.codeview-expand:hover,
158170
.codeview-expand:focus {
159171
background: rgba(255, 255, 255, 0.08);
172+
}
173+
174+
@keyframes spin {
175+
to {
176+
transform: rotate(360deg);
177+
}
160178
}

webviews/sessionLogView/messages.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export interface LoadedMessage {
2323
logs: string;
2424
}
2525

26+
export interface UpdateMessage {
27+
type: 'update';
28+
info: SessionInfo;
29+
logs: string;
30+
}
31+
2632
export interface ChangeThemeMessage {
2733
type: 'changeTheme';
2834
themeData: any;

webviews/sessionLogView/sessionView.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type monacoType from 'monaco-editor';
88
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main';
99
import * as React from 'react';
1010
import * as ReactDOM from 'react-dom';
11+
import { Temporal } from 'temporal-polyfill';
1112
import { vscode } from '../common/message';
1213
import { CodeView } from './codeView';
1314
import './index.css'; // Create this file for styling
@@ -25,6 +26,11 @@ export const SessionView: React.FC<SessionViewProps> = (props) => {
2526
<div className="session-container">
2627
<SessionHeader info={props.info} pullInfo={props.pullInfo} />
2728
<SessionLog logs={props.logs} />
29+
{props.info.state === 'in_progress' && (
30+
<div className="session-in-progress-indicator">
31+
<span className="icon"><i className="codicon codicon-loading"></i></span>
32+
Session is in progress...</div>
33+
)}
2834
</div>
2935
);
3036
};
@@ -36,10 +42,11 @@ interface SessionHeaderProps {
3642
}
3743

3844
const SessionHeader: React.FC<SessionHeaderProps> = ({ info, pullInfo }) => {
39-
const createdAt = new Date(info.created_at);
40-
const completedAt = info.completed_at ? new Date(info.completed_at) : new Date();
41-
const durationMs = completedAt.getTime() - createdAt.getTime();
42-
const durationSec = Math.round(durationMs / 1000);
45+
const createdAt = Temporal.Instant.from(info.created_at);
46+
const completedAt = info.completed_at ? Temporal.Instant.from(info.completed_at) : undefined;
47+
const duration = completedAt && completedAt.epochMilliseconds > 0
48+
? completedAt.since(createdAt, { smallestUnit: 'second', largestUnit: 'hour' })
49+
: undefined;
4350

4451
return (
4552
<header className="session-header">
@@ -73,10 +80,12 @@ const SessionHeader: React.FC<SessionHeaderProps> = ({ info, pullInfo }) => {
7380
<div className="session-value">{info.state}</div>
7481
</div>
7582

76-
<div className="session-duration">
77-
<div className="session-label">Duration</div>
78-
<div className="session-value">{durationSec}s</div>
79-
</div>
83+
{duration && (
84+
<div className="session-duration">
85+
<div className="session-label">Duration</div>
86+
<div className="session-value">{duration.toLocaleString()}</div>
87+
</div>
88+
)}
8089

8190
<div className="session-premium">
8291
<div className="session-label">Premium requests</div>

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6144,6 +6144,18 @@ temp@0.9.4:
61446144
mkdirp "^0.5.1"
61456145
rimraf "~2.6.2"
61466146

6147+
temporal-polyfill@^0.3.0:
6148+
version "0.3.0"
6149+
resolved "https://registry.yarnpkg.com/temporal-polyfill/-/temporal-polyfill-0.3.0.tgz#7fe90e913ac5ec8e0d508fb50d04dd7a74cec23e"
6150+
integrity sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g==
6151+
dependencies:
6152+
temporal-spec "0.3.0"
6153+
6154+
temporal-spec@0.3.0:
6155+
version "0.3.0"
6156+
resolved "https://registry.yarnpkg.com/temporal-spec/-/temporal-spec-0.3.0.tgz#8c4210c575fb28ba0a1c2e02ad68d1be5956a11f"
6157+
integrity sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ==
6158+
61476159
terser-webpack-plugin@5.1.1:
61486160
version "5.1.1"
61496161
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673"

0 commit comments

Comments
 (0)