querysub 0.684.0 → 0.687.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/package.json +1 -1
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineSchema.ts +10 -12
- package/src/diagnostics/logs/errorTickets/TicketPage.tsx +17 -18
- package/src/diagnostics/logs/errorTickets/autoFixer.ts +5 -5
- package/src/diagnostics/logs/errorTickets/ticketTypes.ts +2 -2
package/package.json
CHANGED
|
@@ -483,21 +483,21 @@ let cachedConfigs: ServiceConfig[] | undefined;
|
|
|
483
483
|
let cachedConfigsTime = 0;
|
|
484
484
|
// Set when something tells us the configs actually changed, which no interval can predict
|
|
485
485
|
let configRefreshRequested = false;
|
|
486
|
-
async function getResyncConfigs(): Promise<{ configs: ServiceConfig[]; refreshed: boolean }> {
|
|
486
|
+
async function getResyncConfigs(machineId: string): Promise<{ configs: ServiceConfig[]; refreshed: boolean }> {
|
|
487
487
|
let force = configRefreshRequested;
|
|
488
488
|
if (!force && cachedConfigs && Date.now() - cachedConfigsTime < CONFIG_REFRESH_INTERVAL) {
|
|
489
489
|
return { configs: cachedConfigs, refreshed: false };
|
|
490
490
|
}
|
|
491
491
|
// Cleared before the read, so a change arriving DURING it is not swallowed by our own success
|
|
492
492
|
configRefreshRequested = false;
|
|
493
|
-
cachedConfigs = await getEffectiveServiceConfigs();
|
|
493
|
+
cachedConfigs = await getEffectiveServiceConfigs(machineId);
|
|
494
494
|
cachedConfigsTime = Date.now();
|
|
495
495
|
return { configs: cachedConfigs, refreshed: true };
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
const resyncServicesBase = runInSerial(measureWrap(async function resyncServices() {
|
|
499
499
|
let machineId = getOwnMachineId(getDomain());
|
|
500
|
-
let { configs: allConfigs, refreshed } = await getResyncConfigs();
|
|
500
|
+
let { configs: allConfigs, refreshed } = await getResyncConfigs(machineId);
|
|
501
501
|
if (refreshed) {
|
|
502
502
|
console.log(magenta("Resyncing services, with freshly read configs"));
|
|
503
503
|
}
|
|
@@ -329,6 +329,7 @@ export class MachineServiceControllerBase {
|
|
|
329
329
|
}
|
|
330
330
|
public async setMachineConfig(machineId: string, config: MachineConfig) {
|
|
331
331
|
await machineConfigs.set(machineId, config);
|
|
332
|
+
void this.notifyMachines([machineId], []);
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
private async notifyMachines(newMachineIds: string[], oldMachineIds: string[]) {
|
|
@@ -589,22 +590,19 @@ export class MachineServiceControllerBase {
|
|
|
589
590
|
}
|
|
590
591
|
}
|
|
591
592
|
|
|
592
|
-
|
|
593
|
+
/** The service configs as `machineId` should see them. The machine's own config is a modifier on top: a disabled machine sees every service as not deployed, so it takes them down through the ordinary undeploy path instead of needing a second way to stop things. */
|
|
594
|
+
export async function getEffectiveServiceConfigs(machineId: string): Promise<ServiceConfig[]> {
|
|
593
595
|
let configs = await serviceConfigs.values();
|
|
594
|
-
let
|
|
595
|
-
|
|
596
|
-
let disabledMachineIds = new Set<string>();
|
|
597
|
-
for (let machineConfig of allMachineConfigs) {
|
|
598
|
-
if (machineConfig.disabled) {
|
|
599
|
-
disabledMachineIds.add(machineConfig.machineId);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
596
|
+
let machineConfig = await machineConfigs.get(machineId);
|
|
597
|
+
let disabled = machineConfig?.disabled;
|
|
602
598
|
|
|
603
599
|
return configs.map(config => {
|
|
604
600
|
normalizeServiceConfig(config);
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
601
|
+
if (disabled) {
|
|
602
|
+
config.parameters.deploy = false;
|
|
603
|
+
if (config.oldParameters) {
|
|
604
|
+
config.oldParameters.deploy = false;
|
|
605
|
+
}
|
|
608
606
|
}
|
|
609
607
|
return config;
|
|
610
608
|
});
|
|
@@ -32,6 +32,7 @@ const COMMENT_TEXTAREA_COLLAPSED_HEIGHT = 40;
|
|
|
32
32
|
export const STATE_HUES: Record<TicketState, number> = {
|
|
33
33
|
"investigation": 45,
|
|
34
34
|
"code-change": 210,
|
|
35
|
+
"manual-fix-required": 20,
|
|
35
36
|
"fixed": 120,
|
|
36
37
|
"not-a-bug": 280,
|
|
37
38
|
"confused": 330,
|
|
@@ -331,9 +332,6 @@ class TicketDetail extends qreact.Component<{ ticketId: string }> {
|
|
|
331
332
|
|
|
332
333
|
let sortedComments = [...ticket.comments];
|
|
333
334
|
sort(sortedComments, x => -x.time);
|
|
334
|
-
// Chronological, because patches on the same file build on each other in the order they were proposed.
|
|
335
|
-
let patchComments = ticket.comments.filter(c => c.kind === "patch" && c.patchFiles);
|
|
336
|
-
sort(patchComments, x => x.time);
|
|
337
335
|
return <div className={css.vbox(16).pad2(16).fillBoth.maxWidth("100%").minHeight(0)}>
|
|
338
336
|
<div className={css.hbox(16).alignItems("center")}>
|
|
339
337
|
<ATag values={[ticketIdURL.getOverride("")]}>← All Tickets</ATag>
|
|
@@ -411,21 +409,6 @@ class TicketDetail extends qreact.Component<{ ticketId: string }> {
|
|
|
411
409
|
<div className={css.vbox(12).fillWidth.flexGrow(1).minHeight(0).overflowAuto}>
|
|
412
410
|
<div className={css.hbox(16).alignItems("center")}>
|
|
413
411
|
<h3 className={css.margin(0)}>Comments ({sortedComments.length})</h3>
|
|
414
|
-
{patchComments.length > 0 && (
|
|
415
|
-
<Button
|
|
416
|
-
hue={120}
|
|
417
|
-
onClick={() => {
|
|
418
|
-
let ticketId = this.props.ticketId;
|
|
419
|
-
let commentIds = patchComments.map(c => c.id);
|
|
420
|
-
Querysub.onCommitFinished(async () => {
|
|
421
|
-
await getController().applyPatches.promise(ticketId, commentIds);
|
|
422
|
-
resetTicketData();
|
|
423
|
-
});
|
|
424
|
-
}}
|
|
425
|
-
>
|
|
426
|
-
Apply All Patches ({patchComments.length})
|
|
427
|
-
</Button>
|
|
428
|
-
)}
|
|
429
412
|
</div>
|
|
430
413
|
{sortedComments.map(comment => (
|
|
431
414
|
<TicketCommentItem key={comment.id} ticketId={this.props.ticketId} comment={comment} />
|
|
@@ -661,6 +644,22 @@ class TicketCommentItem extends qreact.Component<{
|
|
|
661
644
|
>
|
|
662
645
|
Delete
|
|
663
646
|
</Button>
|
|
647
|
+
{comment.kind === "patch" && comment.patchFiles && (
|
|
648
|
+
<Button
|
|
649
|
+
hue={120}
|
|
650
|
+
flavor="small"
|
|
651
|
+
onClick={() => {
|
|
652
|
+
let ticketId = this.props.ticketId;
|
|
653
|
+
let commentId = comment.id;
|
|
654
|
+
Querysub.onCommitFinished(async () => {
|
|
655
|
+
await getController().applyPatches.promise(ticketId, [commentId]);
|
|
656
|
+
resetTicketData();
|
|
657
|
+
});
|
|
658
|
+
}}
|
|
659
|
+
>
|
|
660
|
+
Apply Patch
|
|
661
|
+
</Button>
|
|
662
|
+
)}
|
|
664
663
|
{searchInfo && (
|
|
665
664
|
<>
|
|
666
665
|
<span className={css.fontSize(12).whiteSpace("nowrap")}>{searchInfo.rangeText}</span>
|
|
@@ -399,11 +399,11 @@ Query syntax (case-insensitive substring match by default):
|
|
|
399
399
|
},
|
|
400
400
|
{
|
|
401
401
|
name: "setTicketState",
|
|
402
|
-
description: `Set the state of the ticket you are investigating. Use "code-change" once you have proposed patches, "not-a-bug" if the error should simply be ignored, or "confused" if you genuinely cannot figure out what is going on.`,
|
|
402
|
+
description: `Set the state of the ticket you are investigating. Use "code-change" once you have proposed patches, "manual-fix-required" if you know what is wrong but cannot express the fix as a patch, "not-a-bug" if the error should simply be ignored, or "confused" if you genuinely cannot figure out what is going on.`,
|
|
403
403
|
inputSchema: {
|
|
404
404
|
type: "object",
|
|
405
405
|
properties: {
|
|
406
|
-
state: { type: "string", enum: ["code-change", "not-a-bug", "confused"] },
|
|
406
|
+
state: { type: "string", enum: ["code-change", "manual-fix-required", "not-a-bug", "confused"] },
|
|
407
407
|
},
|
|
408
408
|
required: ["state"],
|
|
409
409
|
},
|
|
@@ -504,8 +504,8 @@ async function callTool(toolName: string, args: Record<string, unknown>): Promis
|
|
|
504
504
|
result = await getNodeInfos();
|
|
505
505
|
} else if (toolName === "setTicketState") {
|
|
506
506
|
let state = String(args.state ?? "") as TicketState;
|
|
507
|
-
if (state !== "code-change" && state !== "not-a-bug" && state !== "confused") {
|
|
508
|
-
throw new Error(`setTicketState only allows "code-change", "not-a-bug", or "confused"`);
|
|
507
|
+
if (state !== "code-change" && state !== "manual-fix-required" && state !== "not-a-bug" && state !== "confused") {
|
|
508
|
+
throw new Error(`setTicketState only allows "code-change", "manual-fix-required", "not-a-bug", or "confused"`);
|
|
509
509
|
}
|
|
510
510
|
let service = await ticketService();
|
|
511
511
|
await service.setTicketState(ticketId, state);
|
|
@@ -673,7 +673,7 @@ PHASE 2 — FIX. Only after you have added your diagnosis comment, decide on the
|
|
|
673
673
|
3. Gathering more information: if you could NOT determine the root cause from the available logs, propose patches that ADD logging statements to the relevant code paths so the next investigation has the information it needs.
|
|
674
674
|
Each patch file entry is { file, oldText, newText }: oldText must be copied exactly from the current file contents and should be unique within the file; it is replaced with newText. An empty oldText creates a new file. Relative file paths are resolved against the application repository (${process.cwd()}) first, then against the querysub repository (${QUERYSUB_ROOT}) — whichever contains the file. Absolute paths also work. Keep patches minimal and follow the style of the surrounding code.
|
|
675
675
|
|
|
676
|
-
When you are done, call mcp__autofixer__setTicketState with "code-change" if you proposed patches, or "not-a-bug" if the error should simply be ignored without any code change. If you are confused and just cannot figure out what is going on — the logs and code don't add up, and you can't even propose useful additional logging — add a comment explaining what you tried and what confused you, then call mcp__autofixer__setTicketState with "confused". Do NOT edit files directly — only propose changes through mcp__autofixer__addPatch.
|
|
676
|
+
When you are done, call mcp__autofixer__setTicketState with "code-change" if you proposed patches, or "not-a-bug" if the error should simply be ignored without any code change. If you diagnosed the problem but the fix cannot be expressed as a patch — it needs a design decision, a change outside these repositories, or manual intervention on a machine — add a comment saying exactly what a human has to do, then call mcp__autofixer__setTicketState with "manual-fix-required". If you are confused and just cannot figure out what is going on — the logs and code don't add up, and you can't even propose useful additional logging — add a comment explaining what you tried and what confused you, then call mcp__autofixer__setTicketState with "confused". Do NOT edit files directly — only propose changes through mcp__autofixer__addPatch.
|
|
677
677
|
|
|
678
678
|
==== TICKET ====
|
|
679
679
|
Title: ${ticket.title}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LogDatum } from "../diskLogger";
|
|
2
2
|
|
|
3
|
-
export type TicketState = "investigation" | "code-change" | "fixed" | "not-a-bug" | "confused" | "timed-out";
|
|
4
|
-
export const TICKET_STATES: TicketState[] = ["investigation", "code-change", "fixed", "not-a-bug", "confused", "timed-out"];
|
|
3
|
+
export type TicketState = "investigation" | "code-change" | "manual-fix-required" | "fixed" | "not-a-bug" | "confused" | "timed-out";
|
|
4
|
+
export const TICKET_STATES: TicketState[] = ["investigation", "code-change", "manual-fix-required", "fixed", "not-a-bug", "confused", "timed-out"];
|
|
5
5
|
|
|
6
6
|
export const TICKET_FINAL_STATES: TicketState[] = ["fixed", "not-a-bug"];
|
|
7
7
|
export function isTicketFinished(state: TicketState): boolean {
|