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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.684.0",
3
+ "version": "0.687.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -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
- export async function getEffectiveServiceConfigs(): Promise<ServiceConfig[]> {
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 allMachineConfigs = await machineConfigs.values();
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
- setMachineTargets(config.parameters, getMachineTargets(config.parameters).filter(target => !disabledMachineIds.has(target.machineId)));
606
- if (config.oldParameters) {
607
- setMachineTargets(config.oldParameters, getMachineTargets(config.oldParameters).filter(target => !disabledMachineIds.has(target.machineId)));
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 {