github-issue-tower-defence-management 1.84.1 → 1.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/create-pr.yml +7 -2
- package/CHANGELOG.md +14 -0
- package/README.md +15 -3
- package/bin/adapter/entry-points/cli/index.js +37 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleServer.js +204 -0
- package/bin/adapter/entry-points/console/consoleServer.js.map +1 -0
- package/bin/domain/entities/WorkflowStatus.js +6 -1
- package/bin/domain/entities/WorkflowStatus.js.map +1 -1
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +1 -0
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +126 -0
- package/src/adapter/entry-points/cli/index.ts +66 -0
- package/src/adapter/entry-points/cli/projectConfig.ts +4 -0
- package/src/adapter/entry-points/console/consoleServer.test.ts +297 -0
- package/src/adapter/entry-points/console/consoleServer.ts +220 -0
- package/src/domain/entities/WorkflowStatus.ts +5 -0
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +89 -1
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +2 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleServer.d.ts +19 -0
- package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -0
- package/types/domain/entities/WorkflowStatus.d.ts +1 -0
- package/types/domain/entities/WorkflowStatus.d.ts.map +1 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONSOLE_PORT = 9981;
|
|
6
|
+
|
|
7
|
+
export const CONSOLE_TOKEN_HEADER = 'x-pv-token';
|
|
8
|
+
|
|
9
|
+
const PLACEHOLDER_INDEX_HTML = `<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8" />
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14
|
+
<title>TDPM Console</title>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<main>
|
|
18
|
+
<h1>TDPM Console</h1>
|
|
19
|
+
<p>The console UI bundle has not been built yet.</p>
|
|
20
|
+
</main>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const MIME_TYPES: Record<string, string> = {
|
|
26
|
+
'.html': 'text/html; charset=utf-8',
|
|
27
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
28
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
29
|
+
'.css': 'text/css; charset=utf-8',
|
|
30
|
+
'.json': 'application/json; charset=utf-8',
|
|
31
|
+
'.svg': 'image/svg+xml',
|
|
32
|
+
'.png': 'image/png',
|
|
33
|
+
'.jpg': 'image/jpeg',
|
|
34
|
+
'.jpeg': 'image/jpeg',
|
|
35
|
+
'.gif': 'image/gif',
|
|
36
|
+
'.ico': 'image/x-icon',
|
|
37
|
+
'.map': 'application/json; charset=utf-8',
|
|
38
|
+
'.woff': 'font/woff',
|
|
39
|
+
'.woff2': 'font/woff2',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const hasDotSegment = (requestPath: string): boolean =>
|
|
43
|
+
requestPath
|
|
44
|
+
.split('/')
|
|
45
|
+
.some((segment) => segment.length > 0 && segment.startsWith('.'));
|
|
46
|
+
|
|
47
|
+
export const requiresToken = (requestPath: string): boolean =>
|
|
48
|
+
requestPath.startsWith('/api/') ||
|
|
49
|
+
requestPath === '/api' ||
|
|
50
|
+
requestPath.endsWith('.json');
|
|
51
|
+
|
|
52
|
+
export const isTokenValid = (
|
|
53
|
+
expectedToken: string,
|
|
54
|
+
providedToken: string | null,
|
|
55
|
+
): boolean => providedToken !== null && providedToken === expectedToken;
|
|
56
|
+
|
|
57
|
+
export const extractProvidedToken = (
|
|
58
|
+
queryToken: string | string[] | null,
|
|
59
|
+
headerToken: string | string[] | undefined,
|
|
60
|
+
): string | null => {
|
|
61
|
+
if (typeof queryToken === 'string' && queryToken.length > 0) {
|
|
62
|
+
return queryToken;
|
|
63
|
+
}
|
|
64
|
+
if (typeof headerToken === 'string' && headerToken.length > 0) {
|
|
65
|
+
return headerToken;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const contentTypeForPath = (filePath: string): string => {
|
|
71
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
72
|
+
return MIME_TYPES[extension] ?? 'application/octet-stream';
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const resolveStaticFilePath = (
|
|
76
|
+
uiDistDir: string,
|
|
77
|
+
requestPath: string,
|
|
78
|
+
): string | null => {
|
|
79
|
+
const relativePath = requestPath === '/' ? '/index.html' : requestPath;
|
|
80
|
+
const normalized = path
|
|
81
|
+
.normalize(relativePath)
|
|
82
|
+
.replace(/^(\.\.(\/|\\|$))+/, '');
|
|
83
|
+
const candidate = path.join(uiDistDir, normalized);
|
|
84
|
+
const resolvedRoot = path.resolve(uiDistDir);
|
|
85
|
+
const resolvedCandidate = path.resolve(candidate);
|
|
86
|
+
if (
|
|
87
|
+
resolvedCandidate !== resolvedRoot &&
|
|
88
|
+
!resolvedCandidate.startsWith(resolvedRoot + path.sep)
|
|
89
|
+
) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return resolvedCandidate;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const readStaticFile = (filePath: string): Buffer | null => {
|
|
96
|
+
try {
|
|
97
|
+
const stat = fs.statSync(filePath);
|
|
98
|
+
if (!stat.isFile()) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return fs.readFileSync(filePath);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type ConsoleServerOptions = {
|
|
108
|
+
accessToken: string;
|
|
109
|
+
uiDistDir: string;
|
|
110
|
+
consoleDataOutputDir: string | null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const sendNotFound = (response: http.ServerResponse): void => {
|
|
114
|
+
response.writeHead(404, {
|
|
115
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
116
|
+
'Cache-Control': 'no-store',
|
|
117
|
+
});
|
|
118
|
+
response.end('Not Found');
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const sendUnauthorized = (response: http.ServerResponse): void => {
|
|
122
|
+
response.writeHead(401, {
|
|
123
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
124
|
+
'Cache-Control': 'no-store',
|
|
125
|
+
});
|
|
126
|
+
response.end('Unauthorized');
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const serveBootstrapIndex = (response: http.ServerResponse): void => {
|
|
130
|
+
response.writeHead(200, {
|
|
131
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
132
|
+
'Cache-Control': 'no-store',
|
|
133
|
+
});
|
|
134
|
+
response.end(PLACEHOLDER_INDEX_HTML);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const handleConsoleRequest = (
|
|
138
|
+
options: ConsoleServerOptions,
|
|
139
|
+
request: http.IncomingMessage,
|
|
140
|
+
response: http.ServerResponse,
|
|
141
|
+
): void => {
|
|
142
|
+
const requestUrl = new URL(request.url ?? '/', 'http://localhost');
|
|
143
|
+
const requestPath = requestUrl.pathname;
|
|
144
|
+
|
|
145
|
+
if (hasDotSegment(requestPath)) {
|
|
146
|
+
sendNotFound(response);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (requiresToken(requestPath)) {
|
|
151
|
+
const providedToken = extractProvidedToken(
|
|
152
|
+
requestUrl.searchParams.get('k'),
|
|
153
|
+
request.headers[CONSOLE_TOKEN_HEADER],
|
|
154
|
+
);
|
|
155
|
+
if (!isTokenValid(options.accessToken, providedToken)) {
|
|
156
|
+
sendUnauthorized(response);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
sendNotFound(response);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (requestPath === '/' || requestPath === '/index.html') {
|
|
164
|
+
const indexFilePath = resolveStaticFilePath(
|
|
165
|
+
options.uiDistDir,
|
|
166
|
+
'/index.html',
|
|
167
|
+
);
|
|
168
|
+
const indexContent =
|
|
169
|
+
indexFilePath === null ? null : readStaticFile(indexFilePath);
|
|
170
|
+
if (indexContent === null) {
|
|
171
|
+
serveBootstrapIndex(response);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
response.writeHead(200, {
|
|
175
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
176
|
+
'Cache-Control': 'no-store',
|
|
177
|
+
});
|
|
178
|
+
response.end(indexContent);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const staticFilePath = resolveStaticFilePath(options.uiDistDir, requestPath);
|
|
183
|
+
if (staticFilePath === null) {
|
|
184
|
+
sendNotFound(response);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const staticContent = readStaticFile(staticFilePath);
|
|
188
|
+
if (staticContent === null) {
|
|
189
|
+
sendNotFound(response);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
response.writeHead(200, {
|
|
193
|
+
'Content-Type': contentTypeForPath(staticFilePath),
|
|
194
|
+
'Cache-Control': 'no-store',
|
|
195
|
+
});
|
|
196
|
+
response.end(staticContent);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const createConsoleServer = (
|
|
200
|
+
options: ConsoleServerOptions,
|
|
201
|
+
): http.Server =>
|
|
202
|
+
http.createServer((request, response) => {
|
|
203
|
+
handleConsoleRequest(options, request, response);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export type StartConsoleServerOptions = ConsoleServerOptions & {
|
|
207
|
+
port: number;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const startConsoleServer = (
|
|
211
|
+
options: StartConsoleServerOptions,
|
|
212
|
+
): Promise<http.Server> =>
|
|
213
|
+
new Promise((resolve, reject) => {
|
|
214
|
+
const server = createConsoleServer(options);
|
|
215
|
+
server.once('error', reject);
|
|
216
|
+
server.listen(options.port, () => {
|
|
217
|
+
server.removeListener('error', reject);
|
|
218
|
+
resolve(server);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -8,6 +8,7 @@ export const AWAITING_QUALITY_CHECK_STATUS_NAME = 'Awaiting Quality Check';
|
|
|
8
8
|
export const TODO_STATUS_NAME = 'Todo by human';
|
|
9
9
|
export const PC_TODO_STATUS_NAME = 'PC Todo';
|
|
10
10
|
export const IN_TMUX_STATUS_NAME = 'In Tmux by human';
|
|
11
|
+
export const IN_TMUX_BY_AGENT_STATUS_NAME = 'In Tmux by agent';
|
|
11
12
|
export const DONE_STATUS_NAME = 'Done';
|
|
12
13
|
export const ICEBOX_STATUS_NAME = 'Icebox';
|
|
13
14
|
|
|
@@ -50,6 +51,10 @@ export const REQUIRED_WORKFLOW_STATUSES: WorkflowStatusDefinition[] = [
|
|
|
50
51
|
name: IN_TMUX_STATUS_NAME,
|
|
51
52
|
color: 'RED',
|
|
52
53
|
},
|
|
54
|
+
{
|
|
55
|
+
name: IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
56
|
+
color: 'YELLOW',
|
|
57
|
+
},
|
|
53
58
|
{
|
|
54
59
|
name: DONE_STATUS_NAME,
|
|
55
60
|
color: 'PURPLE',
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
DONE_STATUS_NAME,
|
|
12
12
|
FAILED_PREPARATION_STATUS_NAME,
|
|
13
13
|
ICEBOX_STATUS_NAME,
|
|
14
|
+
IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
14
15
|
IN_TMUX_STATUS_NAME,
|
|
15
16
|
LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
16
17
|
LEGACY_IN_TMUX_STATUS_NAME,
|
|
@@ -75,7 +76,7 @@ const buildIssue = (overrides: Partial<Issue>): Issue => ({
|
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
describe('SetupTowerDefenceProjectUseCase', () => {
|
|
78
|
-
it('should define exactly the
|
|
79
|
+
it('should define exactly the 10 required statuses in the documented order with the documented colors and no descriptions', () => {
|
|
79
80
|
expect(REQUIRED_WORKFLOW_STATUSES).toEqual([
|
|
80
81
|
{ name: DEFAULT_STATUS_NAME, color: 'ORANGE' },
|
|
81
82
|
{ name: AWAITING_WORKSPACE_STATUS_NAME, color: 'BLUE' },
|
|
@@ -84,6 +85,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
84
85
|
{ name: AWAITING_QUALITY_CHECK_STATUS_NAME, color: 'GREEN' },
|
|
85
86
|
{ name: TODO_STATUS_NAME, color: 'PINK' },
|
|
86
87
|
{ name: IN_TMUX_STATUS_NAME, color: 'RED' },
|
|
88
|
+
{ name: IN_TMUX_BY_AGENT_STATUS_NAME, color: 'YELLOW' },
|
|
87
89
|
{ name: DONE_STATUS_NAME, color: 'PURPLE' },
|
|
88
90
|
{ name: ICEBOX_STATUS_NAME, color: 'GRAY' },
|
|
89
91
|
]);
|
|
@@ -252,6 +254,12 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
252
254
|
color: 'RED',
|
|
253
255
|
description: '',
|
|
254
256
|
},
|
|
257
|
+
{
|
|
258
|
+
id: null,
|
|
259
|
+
name: IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
260
|
+
color: 'YELLOW',
|
|
261
|
+
description: '',
|
|
262
|
+
},
|
|
255
263
|
{
|
|
256
264
|
id: null,
|
|
257
265
|
name: DONE_STATUS_NAME,
|
|
@@ -311,6 +319,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
311
319
|
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
312
320
|
TODO_STATUS_NAME,
|
|
313
321
|
IN_TMUX_STATUS_NAME,
|
|
322
|
+
IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
314
323
|
DONE_STATUS_NAME,
|
|
315
324
|
ICEBOX_STATUS_NAME,
|
|
316
325
|
]);
|
|
@@ -420,6 +429,82 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
420
429
|
);
|
|
421
430
|
});
|
|
422
431
|
|
|
432
|
+
it('should create the "In Tmux by agent" status with yellow color when it is missing', async () => {
|
|
433
|
+
const mockProjectRepository =
|
|
434
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
435
|
+
const mockIssueRepository =
|
|
436
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
437
|
+
const statuses = buildCanonicalStatuses().filter(
|
|
438
|
+
(s) => s.name !== IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
439
|
+
);
|
|
440
|
+
const project = buildProject(statuses);
|
|
441
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
442
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
443
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
444
|
+
issues: [],
|
|
445
|
+
cacheUsed: false,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
449
|
+
mockProjectRepository,
|
|
450
|
+
mockIssueRepository,
|
|
451
|
+
);
|
|
452
|
+
await useCase.run({ projectUrl: project.url });
|
|
453
|
+
|
|
454
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
455
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
456
|
+
const inTmuxByAgentEntry = payload.find(
|
|
457
|
+
(s) => s.name === IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
458
|
+
);
|
|
459
|
+
expect(inTmuxByAgentEntry).toBeDefined();
|
|
460
|
+
expect(inTmuxByAgentEntry?.id).toBeNull();
|
|
461
|
+
expect(inTmuxByAgentEntry?.color).toBe('YELLOW');
|
|
462
|
+
const names = payload.map((s) => s.name);
|
|
463
|
+
expect(names.indexOf(IN_TMUX_BY_AGENT_STATUS_NAME)).toBe(
|
|
464
|
+
names.indexOf(IN_TMUX_STATUS_NAME) + 1,
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should leave an already-present "In Tmux by agent" option unchanged by reusing its existing option ID', async () => {
|
|
469
|
+
const mockProjectRepository =
|
|
470
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
471
|
+
const mockIssueRepository =
|
|
472
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
473
|
+
const statuses = buildCanonicalStatuses().map((s) => {
|
|
474
|
+
if (s.name === IN_TMUX_BY_AGENT_STATUS_NAME) {
|
|
475
|
+
return { ...s, id: 'preexisting-agent-id' };
|
|
476
|
+
}
|
|
477
|
+
if (s.name === DEFAULT_STATUS_NAME) {
|
|
478
|
+
return { ...s, description: 'stale description' };
|
|
479
|
+
}
|
|
480
|
+
return s;
|
|
481
|
+
});
|
|
482
|
+
const project = buildProject(statuses);
|
|
483
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
484
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
485
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
486
|
+
issues: [],
|
|
487
|
+
cacheUsed: false,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
491
|
+
mockProjectRepository,
|
|
492
|
+
mockIssueRepository,
|
|
493
|
+
);
|
|
494
|
+
await useCase.run({ projectUrl: project.url });
|
|
495
|
+
|
|
496
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
497
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
498
|
+
const inTmuxByAgentEntry = payload.find(
|
|
499
|
+
(s) => s.name === IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
500
|
+
);
|
|
501
|
+
expect(inTmuxByAgentEntry?.id).toBe('preexisting-agent-id');
|
|
502
|
+
expect(inTmuxByAgentEntry?.color).toBe('YELLOW');
|
|
503
|
+
expect(
|
|
504
|
+
payload.filter((s) => s.name === IN_TMUX_BY_AGENT_STATUS_NAME),
|
|
505
|
+
).toHaveLength(1);
|
|
506
|
+
});
|
|
507
|
+
|
|
423
508
|
it('should remove "PC Todo" from the status list and not include it in others', async () => {
|
|
424
509
|
const mockProjectRepository =
|
|
425
510
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
@@ -530,6 +615,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
530
615
|
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
531
616
|
TODO_STATUS_NAME,
|
|
532
617
|
IN_TMUX_STATUS_NAME,
|
|
618
|
+
IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
533
619
|
DONE_STATUS_NAME,
|
|
534
620
|
ICEBOX_STATUS_NAME,
|
|
535
621
|
]);
|
|
@@ -668,6 +754,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
668
754
|
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
669
755
|
TODO_STATUS_NAME,
|
|
670
756
|
IN_TMUX_STATUS_NAME,
|
|
757
|
+
IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
671
758
|
DONE_STATUS_NAME,
|
|
672
759
|
ICEBOX_STATUS_NAME,
|
|
673
760
|
]);
|
|
@@ -817,6 +904,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
817
904
|
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
818
905
|
TODO_STATUS_NAME,
|
|
819
906
|
IN_TMUX_STATUS_NAME,
|
|
907
|
+
IN_TMUX_BY_AGENT_STATUS_NAME,
|
|
820
908
|
DONE_STATUS_NAME,
|
|
821
909
|
ICEBOX_STATUS_NAME,
|
|
822
910
|
]);
|
|
@@ -24,6 +24,7 @@ const STATUS_OPTIONS: FieldOption[] = [
|
|
|
24
24
|
storyOption('st-aqc', 'Awaiting Quality Check', 'GREEN'),
|
|
25
25
|
storyOption('st-todo', 'Todo by human', 'PINK'),
|
|
26
26
|
storyOption('st-tmux', 'In Tmux by human', 'RED'),
|
|
27
|
+
storyOption('st-tmux-agent', 'In Tmux by agent', 'YELLOW'),
|
|
27
28
|
storyOption('st-done', 'Done', 'PURPLE'),
|
|
28
29
|
storyOption('st-icebox', 'Icebox', 'GRAY'),
|
|
29
30
|
];
|
|
@@ -290,6 +291,7 @@ describe('GenerateConsoleListsUseCase', () => {
|
|
|
290
291
|
'Icebox',
|
|
291
292
|
'Unread',
|
|
292
293
|
'In Tmux by human',
|
|
294
|
+
'In Tmux by agent',
|
|
293
295
|
]) {
|
|
294
296
|
expect(names).not.toContain(excluded);
|
|
295
297
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAgFzB,eAAO,MAAM,OAAO,SAAgB,CAAC"}
|
|
@@ -18,6 +18,7 @@ export type ConfigFile = {
|
|
|
18
18
|
awLogStaleThresholdMinutes?: number;
|
|
19
19
|
labelsAsLlmAgentName?: string[];
|
|
20
20
|
changeTargetPathAliases?: Record<string, string>;
|
|
21
|
+
consoleAccessToken?: string;
|
|
21
22
|
};
|
|
22
23
|
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
23
24
|
export declare const loadConfigFile: (configFilePath: string) => ConfigFile;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectConfig.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/projectConfig.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"projectConfig.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/projectConfig.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAsDF,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CACH,CAAC;AAqBvE,eAAO,MAAM,cAAc,GAAI,gBAAgB,MAAM,KAAG,UAyDvD,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,EACd,aAAa,MAAM,KAClB,UAoEF,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,YAAY,UAAU,EACtB,cAAc,UAAU,EACxB,iBAAiB,UAAU,KAC1B,UAyED,CAAC;AAkBH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CA0DvB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
export declare const DEFAULT_CONSOLE_PORT = 9981;
|
|
3
|
+
export declare const CONSOLE_TOKEN_HEADER = "x-pv-token";
|
|
4
|
+
export declare const hasDotSegment: (requestPath: string) => boolean;
|
|
5
|
+
export declare const requiresToken: (requestPath: string) => boolean;
|
|
6
|
+
export declare const isTokenValid: (expectedToken: string, providedToken: string | null) => boolean;
|
|
7
|
+
export declare const extractProvidedToken: (queryToken: string | string[] | null, headerToken: string | string[] | undefined) => string | null;
|
|
8
|
+
export type ConsoleServerOptions = {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
uiDistDir: string;
|
|
11
|
+
consoleDataOutputDir: string | null;
|
|
12
|
+
};
|
|
13
|
+
export declare const handleConsoleRequest: (options: ConsoleServerOptions, request: http.IncomingMessage, response: http.ServerResponse) => void;
|
|
14
|
+
export declare const createConsoleServer: (options: ConsoleServerOptions) => http.Server;
|
|
15
|
+
export type StartConsoleServerOptions = ConsoleServerOptions & {
|
|
16
|
+
port: number;
|
|
17
|
+
};
|
|
18
|
+
export declare const startConsoleServer: (options: StartConsoleServerOptions) => Promise<http.Server>;
|
|
19
|
+
//# sourceMappingURL=consoleServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consoleServer.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleServer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAI7B,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAmCjD,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,OAGiB,CAAC;AAEtE,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,OAGrB,CAAC;AAEhC,eAAO,MAAM,YAAY,GACvB,eAAe,MAAM,EACrB,eAAe,MAAM,GAAG,IAAI,KAC3B,OAAoE,CAAC;AAExE,eAAO,MAAM,oBAAoB,GAC/B,YAAY,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACpC,aAAa,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,KACzC,MAAM,GAAG,IAQX,CAAC;AAuCF,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,CAAC;AA0BF,eAAO,MAAM,oBAAoB,GAC/B,SAAS,oBAAoB,EAC7B,SAAS,IAAI,CAAC,eAAe,EAC7B,UAAU,IAAI,CAAC,cAAc,KAC5B,IAwDF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,oBAAoB,KAC5B,IAAI,CAAC,MAGJ,CAAC;AAEL,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,SAAS,yBAAyB,KACjC,OAAO,CAAC,IAAI,CAAC,MAAM,CAQlB,CAAC"}
|
|
@@ -7,6 +7,7 @@ export declare const AWAITING_QUALITY_CHECK_STATUS_NAME = "Awaiting Quality Chec
|
|
|
7
7
|
export declare const TODO_STATUS_NAME = "Todo by human";
|
|
8
8
|
export declare const PC_TODO_STATUS_NAME = "PC Todo";
|
|
9
9
|
export declare const IN_TMUX_STATUS_NAME = "In Tmux by human";
|
|
10
|
+
export declare const IN_TMUX_BY_AGENT_STATUS_NAME = "In Tmux by agent";
|
|
10
11
|
export declare const DONE_STATUS_NAME = "Done";
|
|
11
12
|
export declare const ICEBOX_STATUS_NAME = "Icebox";
|
|
12
13
|
export declare const LEGACY_TODO_STATUS_NAME = "Todo";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowStatus.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/WorkflowStatus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAC5C,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,uBAAuB,gBAAgB,CAAC;AACrD,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,kCAAkC,2BAA2B,CAAC;AAC3E,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAChD,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAC7C,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,gBAAgB,SAAS,CAAC;AACvC,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,eAAO,MAAM,uBAAuB,SAAS,CAAC;AAC9C,eAAO,MAAM,0BAA0B,YAAY,CAAC;AACpD,eAAO,MAAM,0CAA0C,4BAC5B,CAAC;AAE5B,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,wBAAwB,
|
|
1
|
+
{"version":3,"file":"WorkflowStatus.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/WorkflowStatus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAC5C,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,uBAAuB,gBAAgB,CAAC;AACrD,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,kCAAkC,2BAA2B,CAAC;AAC3E,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAChD,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAC7C,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,4BAA4B,qBAAqB,CAAC;AAC/D,eAAO,MAAM,gBAAgB,SAAS,CAAC;AACvC,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,eAAO,MAAM,uBAAuB,SAAS,CAAC;AAC9C,eAAO,MAAM,0BAA0B,YAAY,CAAC;AACpD,eAAO,MAAM,0CAA0C,4BAC5B,CAAC;AAE5B,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,wBAAwB,EAyChE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerateConsoleListsUseCase.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACrD,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,oBAAoB,CAAC;AAEhF,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,gBAAgB,CAAC;IACtB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,gBAAgB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,qBAAa,2BAA2B;IACtC,GAAG,GAAI,OAAO,yBAAyB,KAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"GenerateConsoleListsUseCase.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACrD,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,oBAAoB,CAAC;AAEhF,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,gBAAgB,CAAC;IACtB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,gBAAgB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,qBAAa,2BAA2B;IACtC,GAAG,GAAI,OAAO,yBAAyB,KAAG,YAAY,CAsEpD;IAEF,OAAO,CAAC,YAAY,CAKY;IAEhC,OAAO,CAAC,WAAW,CAYhB;IAEH,OAAO,CAAC,iBAAiB,CAYjB;IAER,OAAO,CAAC,sBAAsB,CAQ5B;IAEF,OAAO,CAAC,sBAAsB,CAQ5B;IAEF,OAAO,CAAC,gBAAgB,CAetB;CACH"}
|