@cartanova/qgrid-cli 2.10.3 → 2.10.5
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/bundle/dist/application/qgrid/qgrid-image-generation.js +6 -2
- package/bundle/dist/application/qgrid/qgrid-response-format.js +4 -4
- package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +63 -140
- package/bundle/dist/application/qgrid/qgrid.frame.js +3 -6
- package/bundle/dist/application/qgrid/qgrid.types.js +4 -8
- package/bundle/dist/application/request-log/request-log.model.js +21 -15
- package/bundle/dist/application/setting/setting.model.js +2 -64
- package/bundle/dist/application/setting/setting.store.js +3 -4
- package/bundle/dist/application/setting/setting.types.js +2 -12
- package/bundle/dist/i18n/en.js +1 -3
- package/bundle/dist/i18n/ko.js +1 -3
- package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +19 -8
- package/bundle/src/application/qgrid/qgrid-image-generation.test.ts +18 -0
- package/bundle/src/application/qgrid/qgrid-image-generation.ts +14 -3
- package/bundle/src/application/qgrid/qgrid-response-format.test.ts +9 -0
- package/bundle/src/application/qgrid/qgrid-response-format.ts +10 -7
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +0 -111
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +82 -178
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +0 -38
- package/bundle/src/application/qgrid/qgrid.frame.ts +4 -6
- package/bundle/src/application/qgrid/qgrid.types.ts +14 -4
- package/bundle/src/application/request-log/request-log.model.test.ts +17 -12
- package/bundle/src/application/request-log/request-log.model.ts +8 -2
- package/bundle/src/application/setting/setting.model.test.ts +1 -101
- package/bundle/src/application/setting/setting.model.ts +0 -73
- package/bundle/src/application/setting/setting.store.ts +2 -3
- package/bundle/src/application/setting/setting.types.ts +0 -12
- package/bundle/src/application/sonamu.generated.http +2 -7
- package/bundle/src/i18n/en.ts +0 -3
- package/bundle/src/i18n/ko.ts +0 -3
- package/bundle/src/utils/providers/openai/openai-backend-protocol.test.ts +19 -1
- package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +22 -0
- package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +12 -0
- package/bundle/web-dist/client/assets/cost-CY2BJEPC.js +1 -0
- package/bundle/web-dist/client/assets/index-CvG4UB9v.css +1 -0
- package/bundle/web-dist/client/assets/{index-BIC1sQxw.js → index-Dxw7DiXF.js} +4 -4
- package/bundle/web-dist/client/assets/logs-3n_U4qnI.js +1 -0
- package/bundle/web-dist/client/assets/{monit-CraVV3MQ.js → monit-E8eGW1Sj.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-B3ZBoJ60.js → routes-7spLfIGZ.js} +2 -2
- package/bundle/web-dist/client/assets/services.generated-jnPHYMER.js +51 -0
- package/bundle/web-dist/client/assets/settings-BdosZFNC.js +1 -0
- package/bundle/web-dist/client/assets/{show-ItTExdAh.js → show-b2PFNI3D.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-BynKx8Vn.js → tokens-CBdHb2_U.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-8xw6on5P.js → use-oauth-login-flow-Cyn3YPqW.js} +1 -1
- package/bundle/web-dist/client/assets/{use-update-token-mutation-D-Y8OySK.js → use-update-token-mutation-DQCwCYfR.js} +1 -1
- package/bundle/web-dist/client/index.html +3 -3
- package/bundle/web-dist/server/assets/{cost-CSH_t17I.js → cost-59jBdx-I.js} +8 -3
- package/bundle/web-dist/server/assets/{lazyRouteComponent-Bj1cPq6x.js → lazyRouteComponent-CUR-kWzI.js} +2 -2
- package/bundle/web-dist/server/assets/{logs-BSk9lOHe.js → logs-CaaWO9-Z.js} +9 -7
- package/bundle/web-dist/server/assets/{monit-D9W4ioUi.js → monit-CBil92fJ.js} +4 -4
- package/bundle/web-dist/server/assets/{plus-CmvbWuDf.js → plus-Bvdj8Pwx.js} +1 -1
- package/bundle/web-dist/server/assets/{react-dom-BjR1Iaz8.js → react-dom-CyuShs9K.js} +1 -1
- package/bundle/web-dist/server/assets/{refresh-cw-B8U9BL9-.js → refresh-cw-3QAiSWBd.js} +1 -1
- package/bundle/web-dist/server/assets/{routes-C7lB2Q85.js → routes-DtVf9yqo.js} +7 -7
- package/bundle/web-dist/server/assets/{services.generated-BM4X3Ecj.js → services.generated-FPwsG9p3.js} +116 -131
- package/bundle/web-dist/server/assets/{settings-BFVuldch.js → settings-DeHNwoMv.js} +40 -251
- package/bundle/web-dist/server/assets/{show-Cswh_tqs.js → show-8NNdRxhM.js} +3 -3
- package/bundle/web-dist/server/assets/{tokens-DiTUwUwr.js → tokens-odgS2-9q.js} +4 -4
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-DYKnG4A2.js → use-oauth-login-flow-DTQKdYBO.js} +1 -1
- package/bundle/web-dist/server/assets/{use-update-token-mutation-DqGMopII.js → use-update-token-mutation-xvXbPrCI.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +10 -10
- package/package.json +3 -2
- package/skills/qgrid/SKILL.md +1 -1
- package/skills/qgrid/references/ai-sdk-provider-contract.md +1 -1
- package/skills/qgrid/references/cli-env-and-server-boot.md +12 -9
- package/skills/qgrid/references/decision-rationale.md +2 -2
- package/skills/qgrid/references/openai-codex-runtime.md +5 -1
- package/bundle/dist/utils/process-supervisor.js +0 -22
- package/bundle/dist/utils/server-restart.js +0 -61
- package/bundle/src/utils/process-supervisor.test.ts +0 -24
- package/bundle/src/utils/process-supervisor.ts +0 -17
- package/bundle/src/utils/server-restart.test.ts +0 -76
- package/bundle/src/utils/server-restart.ts +0 -68
- package/bundle/web-dist/client/assets/cost-CpjVoAqp.js +0 -1
- package/bundle/web-dist/client/assets/index-D_sagzsR.css +0 -1
- package/bundle/web-dist/client/assets/logs-zvdujZWn.js +0 -1
- package/bundle/web-dist/client/assets/services.generated-Cbvg2sGQ.js +0 -51
- package/bundle/web-dist/client/assets/settings-DwbzT5jo.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as require_react, O as useQueryClient, X as __toESM, a as SettingService, k as require_jsx_runtime, o as TokenService } from "./services.generated-FPwsG9p3.js";
|
|
2
2
|
import { l as lucideRotateCcw } from "../entry-server.generated.js";
|
|
3
|
-
import { t as useUpdateTokenMutation } from "./use-update-token-mutation-
|
|
4
|
-
import { t as lucidePlus } from "./plus-
|
|
3
|
+
import { t as useUpdateTokenMutation } from "./use-update-token-mutation-xvXbPrCI.js";
|
|
4
|
+
import { t as lucidePlus } from "./plus-Bvdj8Pwx.js";
|
|
5
5
|
//#region ~icons/lucide/eye.jsx
|
|
6
6
|
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
7
7
|
var import_jsx_runtime = require_jsx_runtime();
|
|
@@ -62,7 +62,6 @@ var GROUP_LABELS = {
|
|
|
62
62
|
slack: "Slack 알림",
|
|
63
63
|
slackConnection: "Slack 연결"
|
|
64
64
|
};
|
|
65
|
-
var RESTART_READY_TIMEOUT_MS = 180 * 1e3;
|
|
66
65
|
function maskValue(value) {
|
|
67
66
|
if (!value) return "";
|
|
68
67
|
if (value.length <= 12) return "•".repeat(value.length);
|
|
@@ -357,212 +356,6 @@ function TriggerReminderButton() {
|
|
|
357
356
|
})]
|
|
358
357
|
});
|
|
359
358
|
}
|
|
360
|
-
/**
|
|
361
|
-
* 재시작은 스스로 종료하고 감독자(pm2)가 다시 띄우는 것에 맡긴다. 되돌릴 수 없고 1~2분간
|
|
362
|
-
* 요청이 실패하므로 확인 단계를 둔다 — 무엇이 일어나는지 모달이 먼저 설명한다.
|
|
363
|
-
*/
|
|
364
|
-
function RestartPanel({ supervisor }) {
|
|
365
|
-
const [confirming, setConfirming] = (0, import_react.useState)(false);
|
|
366
|
-
const [restartRequested, setRestartRequested] = (0, import_react.useState)(false);
|
|
367
|
-
const [restartStartedAt, setRestartStartedAt] = (0, import_react.useState)(null);
|
|
368
|
-
const [sawUnavailable, setSawUnavailable] = (0, import_react.useState)(false);
|
|
369
|
-
const [completing, setCompleting] = (0, import_react.useState)(false);
|
|
370
|
-
const [error, setError] = (0, import_react.useState)(null);
|
|
371
|
-
const completingRef = (0, import_react.useRef)(false);
|
|
372
|
-
const restartAttemptRef = (0, import_react.useRef)(0);
|
|
373
|
-
const queryClient = useQueryClient();
|
|
374
|
-
const mutation = SettingService.useRestartServerMutation();
|
|
375
|
-
const resetMutation = mutation.reset;
|
|
376
|
-
const healthQuery = useQuery({
|
|
377
|
-
...QgridService.healthQueryOptions(),
|
|
378
|
-
enabled: restartRequested && !completing,
|
|
379
|
-
refetchInterval: restartRequested && !completing ? 1e3 : false,
|
|
380
|
-
refetchIntervalInBackground: true,
|
|
381
|
-
retry: false
|
|
382
|
-
});
|
|
383
|
-
(0, import_react.useEffect)(() => {
|
|
384
|
-
if (!restartRequested || restartStartedAt === null || completing) return;
|
|
385
|
-
const remaining = Math.max(0, RESTART_READY_TIMEOUT_MS - (Date.now() - restartStartedAt));
|
|
386
|
-
const timeout = window.setTimeout(() => {
|
|
387
|
-
restartAttemptRef.current += 1;
|
|
388
|
-
resetMutation();
|
|
389
|
-
setConfirming(false);
|
|
390
|
-
setRestartRequested(false);
|
|
391
|
-
setRestartStartedAt(null);
|
|
392
|
-
setSawUnavailable(false);
|
|
393
|
-
setError("재시작 후 3분 안에 서버 준비를 확인하지 못했습니다. 상태를 확인한 뒤 다시 시도하세요.");
|
|
394
|
-
}, remaining);
|
|
395
|
-
return () => window.clearTimeout(timeout);
|
|
396
|
-
}, [
|
|
397
|
-
completing,
|
|
398
|
-
resetMutation,
|
|
399
|
-
restartRequested,
|
|
400
|
-
restartStartedAt
|
|
401
|
-
]);
|
|
402
|
-
(0, import_react.useEffect)(() => {
|
|
403
|
-
if (!restartRequested || restartStartedAt === null) return;
|
|
404
|
-
const currentAttemptError = healthQuery.isError && healthQuery.errorUpdatedAt >= restartStartedAt;
|
|
405
|
-
const currentAttemptData = healthQuery.isSuccess && healthQuery.dataUpdatedAt >= restartStartedAt;
|
|
406
|
-
if (currentAttemptError || currentAttemptData && !healthQuery.data.ready) {
|
|
407
|
-
setSawUnavailable(true);
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
if (!sawUnavailable || !currentAttemptData || !healthQuery.data.ready || completingRef.current) return;
|
|
411
|
-
completingRef.current = true;
|
|
412
|
-
setCompleting(true);
|
|
413
|
-
queryClient.invalidateQueries({ queryKey: SettingService.getSettingListQueryOptions().queryKey }).then(() => {
|
|
414
|
-
restartAttemptRef.current += 1;
|
|
415
|
-
resetMutation();
|
|
416
|
-
setConfirming(false);
|
|
417
|
-
setRestartRequested(false);
|
|
418
|
-
setRestartStartedAt(null);
|
|
419
|
-
setSawUnavailable(false);
|
|
420
|
-
}).catch((cause) => {
|
|
421
|
-
restartAttemptRef.current += 1;
|
|
422
|
-
resetMutation();
|
|
423
|
-
setConfirming(false);
|
|
424
|
-
setRestartRequested(false);
|
|
425
|
-
setRestartStartedAt(null);
|
|
426
|
-
setSawUnavailable(false);
|
|
427
|
-
setError(`서버는 준비됐지만 설정을 새로고침하지 못했습니다: ${cause.message}`);
|
|
428
|
-
}).finally(() => {
|
|
429
|
-
completingRef.current = false;
|
|
430
|
-
setCompleting(false);
|
|
431
|
-
});
|
|
432
|
-
}, [
|
|
433
|
-
healthQuery.dataUpdatedAt,
|
|
434
|
-
healthQuery.data?.ready,
|
|
435
|
-
healthQuery.errorUpdatedAt,
|
|
436
|
-
healthQuery.isError,
|
|
437
|
-
healthQuery.isSuccess,
|
|
438
|
-
queryClient,
|
|
439
|
-
resetMutation,
|
|
440
|
-
restartStartedAt,
|
|
441
|
-
restartRequested,
|
|
442
|
-
sawUnavailable
|
|
443
|
-
]);
|
|
444
|
-
const restart = async () => {
|
|
445
|
-
const attempt = restartAttemptRef.current + 1;
|
|
446
|
-
restartAttemptRef.current = attempt;
|
|
447
|
-
setError(null);
|
|
448
|
-
setSawUnavailable(false);
|
|
449
|
-
setCompleting(false);
|
|
450
|
-
completingRef.current = false;
|
|
451
|
-
setRestartRequested(true);
|
|
452
|
-
setRestartStartedAt(Date.now());
|
|
453
|
-
try {
|
|
454
|
-
await mutation.mutateAsync();
|
|
455
|
-
} catch (e) {
|
|
456
|
-
if (restartAttemptRef.current !== attempt) return;
|
|
457
|
-
if (isSonamuError(e) ? e.code >= 400 && e.code < 500 : isAxiosError(e) && e.response !== void 0 && e.response.status >= 400 && e.response.status < 500) {
|
|
458
|
-
restartAttemptRef.current += 1;
|
|
459
|
-
resetMutation();
|
|
460
|
-
setConfirming(false);
|
|
461
|
-
setRestartRequested(false);
|
|
462
|
-
setRestartStartedAt(null);
|
|
463
|
-
setError(e.message || "재시작 요청이 거부되었습니다.");
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
const closeConfirmation = () => {
|
|
468
|
-
if (restartRequested) return;
|
|
469
|
-
setConfirming(false);
|
|
470
|
-
setError(null);
|
|
471
|
-
};
|
|
472
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
473
|
-
className: "panel overflow-hidden",
|
|
474
|
-
children: [
|
|
475
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
476
|
-
className: "panel-header px-4 py-2.5 sm:px-5",
|
|
477
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
478
|
-
className: "text-[13px] font-medium text-sand-700",
|
|
479
|
-
children: "서버 제어"
|
|
480
|
-
})
|
|
481
|
-
}),
|
|
482
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
483
|
-
className: "px-4 py-3 sm:px-5 flex items-start justify-between gap-4",
|
|
484
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
485
|
-
className: "min-w-0",
|
|
486
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
487
|
-
className: "text-[13px] text-sand-800",
|
|
488
|
-
children: "qgrid 재시작"
|
|
489
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
490
|
-
className: "mt-0.5 text-[11px] text-sand-500",
|
|
491
|
-
children: supervisor ? "재시작 필요 설정을 반영하고 qgrid·Codex·Claude CLI를 확인합니다" : "로컬 실행 환경에서는 재시작할 수 없습니다"
|
|
492
|
-
})]
|
|
493
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
494
|
-
type: "button",
|
|
495
|
-
disabled: !supervisor || restartRequested || mutation.isPending,
|
|
496
|
-
onClick: () => setConfirming(true),
|
|
497
|
-
className: "shrink-0 px-2.5 py-1.5 text-[11px] font-medium rounded-lg border border-red-200 text-red-600 hover:bg-red-50 disabled:opacity-40 disabled:hover:bg-transparent transition-colors duration-150",
|
|
498
|
-
children: "재시작"
|
|
499
|
-
})]
|
|
500
|
-
}),
|
|
501
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
502
|
-
className: "px-4 pb-3 sm:px-5 text-[11px] text-red-500",
|
|
503
|
-
children: error
|
|
504
|
-
}),
|
|
505
|
-
confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
506
|
-
className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
|
|
507
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
508
|
-
className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
|
|
509
|
-
onClick: closeConfirmation,
|
|
510
|
-
onKeyDown: () => {}
|
|
511
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
512
|
-
className: "relative panel shadow-xl w-full max-w-sm mx-4",
|
|
513
|
-
children: [
|
|
514
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
515
|
-
className: "px-5 py-4 border-b border-sand-100/60",
|
|
516
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
|
|
517
|
-
className: "text-base font-medium text-sand-900",
|
|
518
|
-
children: restartRequested ? mutation.isPending ? "재시작 요청 중" : "서버 재연결 중" : "qgrid 재시작"
|
|
519
|
-
})
|
|
520
|
-
}),
|
|
521
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
522
|
-
className: "px-5 py-4 space-y-2",
|
|
523
|
-
children: [
|
|
524
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
525
|
-
className: "text-[13px] text-sand-700",
|
|
526
|
-
children: [
|
|
527
|
-
"워커가 다시 뜰 때까지 ",
|
|
528
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", {
|
|
529
|
-
className: "text-sand-900",
|
|
530
|
-
children: "1~2분간 요청이 실패"
|
|
531
|
-
}),
|
|
532
|
-
"합니다."
|
|
533
|
-
]
|
|
534
|
-
}),
|
|
535
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
536
|
-
className: "text-[12px] text-sand-500",
|
|
537
|
-
children: "서버를 종료하면 모니터링 도구가 다시 띄웁니다. 이때 재시작 필요 설정을 반영하고, qgrid와 Codex CLI·Claude Code의 최신 버전을 확인합니다."
|
|
538
|
-
}),
|
|
539
|
-
restartRequested && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
540
|
-
className: "text-[12px] font-medium text-sienna-600",
|
|
541
|
-
children: mutation.isPending ? "종료 요청을 처리하고 있습니다…" : sawUnavailable ? "새 서버가 준비될 때까지 연결을 확인하고 있습니다…" : "서버 종료를 기다리고 있습니다…"
|
|
542
|
-
})
|
|
543
|
-
]
|
|
544
|
-
}),
|
|
545
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
546
|
-
className: "px-5 py-3 border-t border-sand-100/60 flex justify-end gap-2",
|
|
547
|
-
children: [!restartRequested && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
548
|
-
type: "button",
|
|
549
|
-
onClick: closeConfirmation,
|
|
550
|
-
className: "px-3 py-1.5 text-[12px] rounded-lg text-sand-600 hover:bg-sand-100 transition-colors duration-150",
|
|
551
|
-
children: "취소"
|
|
552
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
553
|
-
type: "button",
|
|
554
|
-
disabled: restartRequested || mutation.isPending,
|
|
555
|
-
onClick: () => void restart(),
|
|
556
|
-
className: "px-3 py-1.5 text-[12px] font-medium rounded-lg bg-red-500 text-white hover:bg-red-600 disabled:opacity-50 transition-colors duration-150",
|
|
557
|
-
children: restartRequested ? mutation.isPending ? "요청 중" : "재연결 중" : "재시작"
|
|
558
|
-
})]
|
|
559
|
-
})
|
|
560
|
-
]
|
|
561
|
-
})]
|
|
562
|
-
})
|
|
563
|
-
]
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
359
|
function SettingsPanel() {
|
|
567
360
|
const settingsQuery = SettingService.useSettingList();
|
|
568
361
|
const { data, isLoading } = settingsQuery;
|
|
@@ -611,53 +404,49 @@ function SettingsPanel() {
|
|
|
611
404
|
const groups = [...new Set(settings.map((s) => s.group))];
|
|
612
405
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
613
406
|
className: "space-y-4",
|
|
614
|
-
children: [
|
|
615
|
-
|
|
407
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
408
|
+
className: "panel overflow-hidden",
|
|
409
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
410
|
+
className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
|
|
411
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
412
|
+
className: "text-[13px] font-medium text-sand-700",
|
|
413
|
+
children: "환경"
|
|
414
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
415
|
+
className: "text-[11px] text-sand-400",
|
|
416
|
+
children: "기동 시점 값"
|
|
417
|
+
})]
|
|
418
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
|
|
419
|
+
className: "px-4 py-3 sm:px-5 grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1.5",
|
|
420
|
+
children: runtime.map((info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
421
|
+
className: "flex items-baseline justify-between gap-3 min-w-0",
|
|
422
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
423
|
+
className: "text-[11px] text-sand-500 shrink-0",
|
|
424
|
+
children: info.label
|
|
425
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
|
|
426
|
+
className: "text-[12px] font-mono text-sand-700 truncate",
|
|
427
|
+
children: info.value
|
|
428
|
+
})]
|
|
429
|
+
}, info.label))
|
|
430
|
+
})]
|
|
431
|
+
}), groups.map((group) => {
|
|
432
|
+
const items = settings.filter((s) => s.group === group);
|
|
433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
616
434
|
className: "panel overflow-hidden",
|
|
617
435
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
618
436
|
className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
|
|
619
437
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
620
438
|
className: "text-[13px] font-medium text-sand-700",
|
|
621
|
-
children:
|
|
622
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
className: "flex items-baseline justify-between gap-3 min-w-0",
|
|
630
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
631
|
-
className: "text-[11px] text-sand-500 shrink-0",
|
|
632
|
-
children: info.label
|
|
633
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
|
|
634
|
-
className: "text-[12px] font-mono text-sand-700 truncate",
|
|
635
|
-
children: info.value
|
|
636
|
-
})]
|
|
637
|
-
}, info.label))
|
|
439
|
+
children: GROUP_LABELS[group] ?? group
|
|
440
|
+
}), group === "slack" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriggerReminderButton, {})]
|
|
441
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
442
|
+
className: "divide-y divide-sand-100/80",
|
|
443
|
+
children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingRow, {
|
|
444
|
+
item,
|
|
445
|
+
disabled: item.key === "qgrid.tokenWindowKeepaliveEnabled" && !data?.keepaliveRunnerEnabled
|
|
446
|
+
}), item.key === "qgrid.tokenWindowKeepaliveEnabled" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeepaliveTokenSettings, { runnerEnabled: data?.keepaliveRunnerEnabled ?? false })] }, item.key))
|
|
638
447
|
})]
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
const items = settings.filter((s) => s.group === group);
|
|
642
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
643
|
-
className: "panel overflow-hidden",
|
|
644
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
645
|
-
className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
|
|
646
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
647
|
-
className: "text-[13px] font-medium text-sand-700",
|
|
648
|
-
children: GROUP_LABELS[group] ?? group
|
|
649
|
-
}), group === "slack" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriggerReminderButton, {})]
|
|
650
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
651
|
-
className: "divide-y divide-sand-100/80",
|
|
652
|
-
children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingRow, {
|
|
653
|
-
item,
|
|
654
|
-
disabled: item.key === "qgrid.tokenWindowKeepaliveEnabled" && !data?.keepaliveRunnerEnabled
|
|
655
|
-
}), item.key === "qgrid.tokenWindowKeepaliveEnabled" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeepaliveTokenSettings, { runnerEnabled: data?.keepaliveRunnerEnabled ?? false })] }, item.key))
|
|
656
|
-
})]
|
|
657
|
-
}, group);
|
|
658
|
-
}),
|
|
659
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartPanel, { supervisor: data?.supervisor ?? null })
|
|
660
|
-
]
|
|
448
|
+
}, group);
|
|
449
|
+
})]
|
|
661
450
|
});
|
|
662
451
|
}
|
|
663
452
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as Link } from "./lazyRouteComponent-
|
|
1
|
+
import { A as require_react, E as useQuery, X as __toESM, i as RequestLogStepService, k as require_jsx_runtime, l as isSonamuError, q as __commonJSMin, r as RequestLogService } from "./services.generated-FPwsG9p3.js";
|
|
2
|
+
import { a as Link } from "./lazyRouteComponent-CUR-kWzI.js";
|
|
3
3
|
import { c as lucideX, d as Markdown, t as Route, u as remarkGfm } from "../entry-server.generated.js";
|
|
4
|
-
import { n as formatMicroUsd, t as cacheHitRate } from "./cost-
|
|
4
|
+
import { n as formatMicroUsd, t as cacheHitRate } from "./cost-59jBdx-I.js";
|
|
5
5
|
import { t as requestModelDisplay } from "./request-model-CnCeaNLE.js";
|
|
6
6
|
//#region ../../node_modules/.pnpm/@babel+runtime@7.29.2/node_modules/@babel/runtime/helpers/extends.js
|
|
7
7
|
var require_extends = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as require_react, O as useQueryClient, X as __toESM, k as require_jsx_runtime, n as QgridService, o as TokenService } from "./services.generated-FPwsG9p3.js";
|
|
2
2
|
import { f as lucideKeyRound, o as Q } from "../entry-server.generated.js";
|
|
3
|
-
import { t as useUpdateTokenMutation } from "./use-update-token-mutation-
|
|
4
|
-
import { n as OAuthCodeEntry, t as useOAuthLoginFlow } from "./use-oauth-login-flow-
|
|
5
|
-
import { t as lucidePlus } from "./plus-
|
|
3
|
+
import { t as useUpdateTokenMutation } from "./use-update-token-mutation-xvXbPrCI.js";
|
|
4
|
+
import { n as OAuthCodeEntry, t as useOAuthLoginFlow } from "./use-oauth-login-flow-DTQKdYBO.js";
|
|
5
|
+
import { t as lucidePlus } from "./plus-Bvdj8Pwx.js";
|
|
6
6
|
//#region src/components/qgrid/AddTokenModal.tsx
|
|
7
7
|
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
|
|
8
8
|
var import_jsx_runtime = require_jsx_runtime();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as require_react, O as useQueryClient, X as __toESM, k as require_jsx_runtime, n as QgridService, o as TokenService } from "./services.generated-FPwsG9p3.js";
|
|
2
2
|
import { o as Q } from "../entry-server.generated.js";
|
|
3
3
|
//#region src/components/qgrid/OAuthCodeEntry.tsx
|
|
4
4
|
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as useMutation, n as QgridService } from "./services.generated-FPwsG9p3.js";
|
|
2
2
|
//#region src/services/token/use-update-token-mutation.ts
|
|
3
3
|
function useUpdateTokenMutation() {
|
|
4
4
|
return useMutation({ mutationFn: ({ id, name, quotaThreshold, weight, keepaliveEnabled }) => QgridService.updateToken(id, name, quotaThreshold, weight, keepaliveEnabled) });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-
|
|
3
|
-
import { n as require_shim, t as require_react_dom } from "./assets/react-dom-
|
|
1
|
+
import { A as require_react, B as matchQuery, C as union, D as QueryClientProvider, F as notifyManager, G as focusManager, H as partialMatchKey, I as functionalUpdate, J as __exportAll, K as Subscribable, L as hashKey, M as infiniteQueryBehavior, N as Query, P as onlineManager, R as hashQueryKeyByOptions, S as string$2, U as resolveStaleTime, V as noop$1, W as skipToken, X as __toESM, Y as __require, _ as custom, b as number$1, c as fetch$1, d as SD, f as SUPPORTED_LOCALES, g as boolean$1, h as array, j as Mutation, k as require_jsx_runtime, m as _enum, n as QgridService, o as TokenService, p as setLocale, q as __commonJSMin, s as SonamuQueryMode, u as zArrayable, v as date, w as unknown, x as object, y as int, z as matchMutation } from "./assets/services.generated-FPwsG9p3.js";
|
|
2
|
+
import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-CUR-kWzI.js";
|
|
3
|
+
import { n as require_shim, t as require_react_dom } from "./assets/react-dom-CyuShs9K.js";
|
|
4
4
|
import minpath from "node:path";
|
|
5
5
|
import minproc from "node:process";
|
|
6
6
|
import { fileURLToPath as urlToPath } from "node:url";
|
|
@@ -46874,7 +46874,7 @@ function ChatWidget() {
|
|
|
46874
46874
|
}
|
|
46875
46875
|
//#endregion
|
|
46876
46876
|
//#region src/components/Sidebar.tsx
|
|
46877
|
-
var qgridVersion = "2.10.
|
|
46877
|
+
var qgridVersion = "2.10.5";
|
|
46878
46878
|
function Sidebar({ className }) {
|
|
46879
46879
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
46880
46880
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
@@ -68752,15 +68752,15 @@ function RootComponent() {
|
|
|
68752
68752
|
}
|
|
68753
68753
|
//#endregion
|
|
68754
68754
|
//#region src/routes/tokens.tsx
|
|
68755
|
-
var $$splitComponentImporter$5 = () => import("./assets/tokens-
|
|
68755
|
+
var $$splitComponentImporter$5 = () => import("./assets/tokens-odgS2-9q.js");
|
|
68756
68756
|
var Route$5 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$5, "component") });
|
|
68757
68757
|
//#endregion
|
|
68758
68758
|
//#region src/routes/settings.tsx
|
|
68759
|
-
var $$splitComponentImporter$4 = () => import("./assets/settings-
|
|
68759
|
+
var $$splitComponentImporter$4 = () => import("./assets/settings-DeHNwoMv.js");
|
|
68760
68760
|
var Route$4 = createFileRoute("/settings")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
|
|
68761
68761
|
//#endregion
|
|
68762
68762
|
//#region src/routes/monit.tsx
|
|
68763
|
-
var $$splitComponentImporter$3 = () => import("./assets/monit-
|
|
68763
|
+
var $$splitComponentImporter$3 = () => import("./assets/monit-CBil92fJ.js");
|
|
68764
68764
|
var monitSearchSchema = object({ level: _enum([
|
|
68765
68765
|
"all",
|
|
68766
68766
|
"warn",
|
|
@@ -69137,7 +69137,7 @@ object({
|
|
|
69137
69137
|
_enum(["A"]);
|
|
69138
69138
|
//#endregion
|
|
69139
69139
|
//#region src/routes/logs.tsx
|
|
69140
|
-
var $$splitComponentImporter$2 = () => import("./assets/logs-
|
|
69140
|
+
var $$splitComponentImporter$2 = () => import("./assets/logs-CaaWO9-Z.js");
|
|
69141
69141
|
var logsSearchSchema = object({
|
|
69142
69142
|
token: string$2().optional(),
|
|
69143
69143
|
project: string$2().optional(),
|
|
@@ -69151,11 +69151,11 @@ var Route$2 = createFileRoute("/logs")({
|
|
|
69151
69151
|
});
|
|
69152
69152
|
//#endregion
|
|
69153
69153
|
//#region src/routes/index.tsx
|
|
69154
|
-
var $$splitComponentImporter$1 = () => import("./assets/routes-
|
|
69154
|
+
var $$splitComponentImporter$1 = () => import("./assets/routes-DtVf9yqo.js");
|
|
69155
69155
|
var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
|
|
69156
69156
|
//#endregion
|
|
69157
69157
|
//#region src/routes/requests/show.tsx
|
|
69158
|
-
var $$splitComponentImporter = () => import("./assets/show-
|
|
69158
|
+
var $$splitComponentImporter = () => import("./assets/show-8NNdRxhM.js");
|
|
69159
69159
|
var showSearchSchema = object({ id: number$1() });
|
|
69160
69160
|
var Route = createFileRoute("/requests/show")({
|
|
69161
69161
|
validateSearch: showSearchSchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cartanova/qgrid-cli",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cartanova-ai/qgrid"
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"node": ">=20"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
|
-
"bundle": "
|
|
62
|
+
"bundle": "cd ../api && pnpm build && cd ../cli && pnpm run bundle:copy",
|
|
63
|
+
"bundle:copy": "pnpm run check:bundle-dependencies && rm -rf bundle && mkdir -p bundle/public && touch bundle/.env && cp -r ../api/dist ./bundle/dist && cp -r ../api/web-dist ./bundle/web-dist && cp -r ../api/src ./bundle/src",
|
|
63
64
|
"build": "tsdown",
|
|
64
65
|
"check:bundle-dependencies": "node tooling/check-bundled-api-dependencies.mjs",
|
|
65
66
|
"postinstall": "node postinstall.mjs",
|
package/skills/qgrid/SKILL.md
CHANGED
|
@@ -54,7 +54,7 @@ Request logging is enabled by default. Use `providerOptions.qgrid.logger: false`
|
|
|
54
54
|
- Prefer `getPuri()` for Sonamu model queries. Treat `getDB()` as a legacy escape hatch only when Puri cannot express the required query. Inside `@transactional` methods, all participating queries must use `getPuri()`; `getDB()` does not reuse Sonamu's ambient transaction connection.
|
|
55
55
|
- Never hand-author migration files for Sonamu-managed schema. In the qgrid source repository, change the entity definition, inspect `sonamu migrate status`, and create migration files with `mise exec -- pnpm --dir packages/api sonamu migrate generate`. Inspect the generated files before applying them. If Sonamu cannot express a required schema change, stop and ask rather than silently replacing its workflow with a custom migration.
|
|
56
56
|
- Keep OpenAI Codex built-in tools, apps, plugins, skills, web search, shell, and environment instruction blocks disabled unless the user explicitly asks for agentic Codex behavior.
|
|
57
|
-
- Treat OpenAI image generation as opt-in outside dashboard chat. In 2.9.8, OpenAI `qgrid_chat` opts into raw API `imageGeneration: "auto"` on every turn; the public AI SDK option remains boolean. Ordinary images use the hosted Responses image tool; a server-executed transparent-image function calls Codex standalone Images directly with subscription credentials. Do not reintroduce a client decision tool followed by another GPT generation request.
|
|
57
|
+
- Treat OpenAI image generation as opt-in outside dashboard chat. In 2.9.8, OpenAI `qgrid_chat` opts into raw API `imageGeneration: "auto"` on every turn; the public AI SDK option remains boolean. Ordinary images use the hosted Responses image tool; a server-executed transparent-image function calls Codex standalone Images directly with subscription credentials. Do not reintroduce a client decision tool followed by another GPT generation request. Hosted image costs are unknown (`null`); standalone usage and known-size legacy responses use public-price estimates. Mark totals as known subtotals when they omit unknown image costs.
|
|
58
58
|
|
|
59
59
|
## Request log IDs (2.10.0+)
|
|
60
60
|
|
|
@@ -71,7 +71,7 @@ Do not write `providerOptions: { ... } satisfies QgridProviderOptions`: the expo
|
|
|
71
71
|
- `timeoutMs`: Anthropic server-side Claude Code process timeout in milliseconds. It must be a positive integer no greater than 30 minutes and defaults to 240 seconds. For non-stream `generateText`, the SDK derives request-scoped Undici `headersTimeout` and `bodyTimeout` values as `timeoutMs + 60_000` without changing the process-global dispatcher.
|
|
72
72
|
- `fallbackModels`: deprecated, ignored compatibility field. The provider emits a standard unsupported-option warning when supplied; remove it from callers. It is not Claude Code's upstream Fable safety-refusal fallback.
|
|
73
73
|
- `imageGeneration`: OpenAI/Codex non-stream only. Enables Codex's built-in `image_generation` tool for that request.
|
|
74
|
-
- `imageGenerationOptions`: optional `quality: "low" | "medium" | "high"`, `size
|
|
74
|
+
- `imageGenerationOptions`: optional `quality: "low" | "medium" | "high"`, `size` (`auto` or positive-integer `WIDTHxHEIGHT`), and `background: "auto" | "opaque" | "transparent"`. Transparent requests use Codex standalone Images with `gpt-image-2`; other requests use the hosted Responses tool. Transparent mode rejects tools/structured output, supports up to five references, validates real transparent and visible PNG pixels, and never invokes background-removal fallback. Size is a positive-integer WIDTHxHEIGHT hint, not an exact-output guarantee; omit it or use auto and put the desired aspect ratio in the prompt. Do not impose public Images API 16-pixel/4K rules on the private subscription route. Both routes expose observations (`codex-responses` or `codex-images`); hosted model is optional. PNG dimensions come from bytes; JPEG/WebP size comes from backend metadata. Hosted image cost is null, and arbitrary/auto size hints never receive a made-up price-table estimate. Returned size/quality may differ from requested settings; read `result.providerMetadata.qgrid.imageGeneration` or per-file `result.content` metadata. `result.files[]` contains bytes/mediaType only. Logs separate `requestedOptions` from `observedGeneration`; image usage is separate from the zero text-driver usage on this route.
|
|
75
75
|
|
|
76
76
|
`providerOptions.qgrid` does not currently support `projectName` or `project_name`. Prefer `QGRID_PROJECT_NAME` for the default project label; use config `projectName` only when a caller needs to override that default.
|
|
77
77
|
|
|
@@ -92,17 +92,20 @@ Some env values are editable at runtime through the dashboard's Settings page, b
|
|
|
92
92
|
- Editable: Slack delivery control (master switch, reminder on/off, reminder interval, quiet-hours window, weekend behaviour), and the Slack channel/user-map/bot-token. `SETTING_DEFS` in `setting.constant.ts` is the single definition — key, env name, type, bounds, and whether the change applies immediately or needs a restart. The `settings` table itself (`setting.entity.json`) only stores `(key, value)` strings; everything the UI needs to render and validate a key lives in that constant, not in the schema.
|
|
93
93
|
- Not editable: anything needed before the server can read its own database (`QGRID_DB_*`, `HOST`, `PORT`, `NODE_ENV`). These are exposed read-only on the same page so an operator can confirm which environment they are looking at; the DB password is masked.
|
|
94
94
|
|
|
95
|
-
###
|
|
95
|
+
### Process lifecycle
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
qgrid does not detect a process supervisor or expose a restart API or dashboard
|
|
98
|
+
control. Restarting, replacing, and recovering the process belong to the
|
|
99
|
+
operator's deployment tooling. Settings marked `restart` still take effect on
|
|
100
|
+
the next process start; the label does not imply an in-app restart action.
|
|
101
|
+
|
|
102
|
+
There is no SIGTERM drain handler. Replacing the process during traffic can
|
|
103
|
+
interrupt active responses, so deployment tooling must account for startup
|
|
104
|
+
readiness and interruption. Ordinary request success/error/abort accounting and
|
|
105
|
+
stale tool-wait cleanup remain independent of the deployment mechanism.
|
|
106
|
+
|
|
107
|
+
### Live notification settings
|
|
98
108
|
|
|
99
|
-
- `detectSupervisor` (`utils/process-supervisor.ts`) accepts only `pm_id` (pm2). `INVOCATION_ID` proves systemd launched a process but does not prove the unit has a restart policy, so it is not sufficient. Without pm2, `restartServer` returns 400 and the button is disabled: exiting with nothing to relaunch the process is a shutdown, not a restart. Presence is the signal, not the value — pm2 sets `pm_id=0` for the first process.
|
|
100
|
-
- Browser requests must be same-host: when `Origin` is present and its host differs from `X-Forwarded-Host`/`Host`, `restartServer` returns 403. Origin-less operational clients such as `curl` remain supported. This is CSRF protection, not a substitute for authenticating the management API at Caddy.
|
|
101
|
-
- The first restart request latches `restartPending`, blocks new native query/stream dispatch with 503, and marks only process-local active native request-log IDs as `error: server restarted`. Tool-result-waiting runs and externally owned logger runs are untouched. This is state reconciliation, not request draining.
|
|
102
|
-
- Exit is process-wide and one-shot. It runs after the restart response emits `finish`; a response `close` or a five-second fallback covers disconnects. Concurrent restart calls do not schedule multiple exits.
|
|
103
|
-
- The supervisor relaunches from `ecosystem.config.cjs`, not from the dying process's argv. That file carries no `--skip-update`, so each restart also runs the CLI self-update — a restart is a deploy. The confirmation dialog says so; dropping the update means adding `--skip-update` there.
|
|
104
|
-
- The relaunch reuses the environment pm2 captured when the app was first started. Neither the dashboard restart nor `pm2 restart qgrid` re-reads `ecosystem.config.cjs`, so an env edit in that file (for example `QGRID_TOKEN_WINDOW_KEEPALIVE_ENABLED`) stays invisible until `pm2 restart ecosystem.config.cjs --update-env` (or `pm2 delete` + `pm2 start`) followed by `pm2 save`. Verify with `GET /api/setting/listSettings`, which reports the env the process actually sees. dev0 ran for a week with the keepalive env in the file but not in the process because of this (2026-09-02).
|
|
105
|
-
- In-flight provider responses are not drained. There is no SIGTERM handler, so a restart during traffic cuts active responses, and the OpenAI pool needs 1–2 minutes before requests succeed again (`QgridDispatcher.startupState` answers 503 in that window). The dashboard stays locked after confirmation, observes an unavailable/not-ready health state, then waits for a later `ready: true` before refreshing settings.
|
|
106
109
|
- Slack channel, bot-token, and user-map changes are read on the next notification. Changing the expiry-reminder interval or toggling the reminder replaces the current timer immediately without sending a notification merely because the setting changed; process boot still performs the intended immediate reminder run.
|
|
107
110
|
- `slack.enabled` is the holiday switch: an operator turns it off for periods no rule can express. It suppresses ordinary notifications but not `urgent` ones, so a provider losing its last token still reports during a long break. `slack.remindersEnabled` is narrower — it stops only the repeat timer and preserves the chosen interval so re-enabling restores it.
|
|
108
111
|
- Quiet hours are `slack.quietFromHour`/`slack.quietUntilHour` (0–23, Asia/Seoul) plus `slack.notifyOnWeekends`. From > until reads as an overnight window (20→8); from < until reads as a same-day window, which night-shift teams may want. Equal values mean no quiet window at all. Out-of-range values fall back to 20/8 at runtime, since env can bypass the stored-value validation.
|
|
@@ -236,9 +236,9 @@ Key decisions:
|
|
|
236
236
|
- Image turns send full input directly and retain no provider conversation state. This prevents base64 payloads from entering reusable state.
|
|
237
237
|
- qgrid performs preflight capability/model gates. Explicit `imageGeneration: true` requires an image; `"auto"` allows normal text replies but still rejects attempted image generation without a completed image. The public AI SDK retains its boolean option and image-required contract.
|
|
238
238
|
- Returned images are inline base64 content parts for consumers and AI SDK `file` parts. Reference images are accepted through AI SDK multimodal message parts only on the `imageGeneration` path, and are transported as JSON data URLs with SDK-side size guarding.
|
|
239
|
-
- `imageGenerationOptions` supports quality, size, and background.
|
|
239
|
+
- `imageGenerationOptions` supports quality, size, and background. Size accepts `auto` or positive-integer `WIDTHxHEIGHT` hints; the subscription backend can ignore them. Put desired ratios in the prompt, report observed output settings, and never silently resize. Hosted image cost is unknown because image usage/model are not reliably exposed. Only legacy responses without observations retain known three-size pricing assumptions. Transparent-route image usage is priced separately, and no background-removal fallback is allowed.
|
|
240
240
|
- qgrid does not manage generated or reference images as durable assets through a `generated_images` table or object-storage layer. Current request logs can still contain inline image data URLs for inspection and synthetic `image_generation` tool-call steps; reference input images live in the first synthetic step's `tool_args.inputImages`.
|
|
241
|
-
- Image cost is stored separately in `request_logs.image_cost_usd`; `request_logs.cost_usd` remains the Codex driver model token cost.
|
|
241
|
+
- Image cost is stored separately in `request_logs.image_cost_usd`; `request_logs.cost_usd` remains the Codex driver model token cost. Hosted image cost is unknown (`null`). Standalone image costs are public-price estimates from reported image usage. Only legacy responses without observations and with known size prices retain size-table estimates. List rows and filtered totals mark known subtotals when image costs are unknown; `totalCost.unknownImageCostCount` counts those requests under the same filters.
|
|
242
242
|
- Inspect current plans, tests, and implementation before changing behavior; the Codex tool surface can change underneath qgrid.
|
|
243
243
|
|
|
244
244
|
The 2.9.8 chat design removes the discarded client `request_image_generation` decision step, which asked GPT to choose a tool and then called GPT again for ordinary generation. Native `image_generation` handles ordinary generation directly. A separate qgrid-defined `generate_transparent_image` function is necessary to reach the standalone transparent endpoint, but the server executes it with the same token and cancellation signal without another GPT request. Routing belongs to the server; the chat only sends context and displays results.
|
|
@@ -179,7 +179,7 @@ Quota is fetched directly from `https://chatgpt.com/backend-api/wham/usage` with
|
|
|
179
179
|
|
|
180
180
|
OpenAI usage reports total input, cached input, output, reasoning, and total tokens for the request. Preserve cached input as a subset of input; do not add it to input again when computing cache-hit rate.
|
|
181
181
|
|
|
182
|
-
Image generation is opt-in and non-stream at the AI SDK interface. The backend still delivers its response through SSE. `imageGenerationOptions.outputFormat` maps to the hosted tool's `output_format` (`png`, `jpeg`, or `webp`); omission retains PNG. Returned bytes determine the MIME type when recognizable, with the reported format as fallback. Preserve that type through qgrid content, AI SDK file parts, and request-log image data URLs. The standalone transparent route remains PNG-only and rejects other output formats.
|
|
182
|
+
Image generation is opt-in and non-stream at the AI SDK interface. The backend still delivers its response through SSE. `imageGenerationOptions.outputFormat` maps to the hosted tool's `output_format` (`png`, `jpeg`, or `webp`); omission retains PNG. Returned bytes determine the MIME type when recognizable, with the reported format as fallback. Preserve that type through qgrid content, AI SDK file parts, and request-log image data URLs. The standalone transparent route remains PNG-only and rejects other output formats. Hosted Responses preserves observed image settings with route `codex-responses`, using PNG byte dimensions or the backend-reported JPEG/WebP size. Unreported model/settings stay absent. Hosted image cost is null because exact image usage/model are unavailable; requested settings cannot establish a price. Legacy responses without observations retain known three-size estimates only.
|
|
183
183
|
|
|
184
184
|
### Transparent image route
|
|
185
185
|
|
|
@@ -196,3 +196,7 @@ Auto mode accepts text-only completion when no image tool was attempted. Explici
|
|
|
196
196
|
Auto transparent turns preserve the Responses driver model and token usage; image model, settings, and usage remain separate generation metadata. Do not apply the explicit standalone route's zero-driver accounting to auto turns. Full image history is replayed on each turn without reusable provider conversation state. Global image-tool opt-in remains unchanged outside chat to preserve regular callers' tool framing and cache behavior.
|
|
197
197
|
|
|
198
198
|
The 2026-09-29 hosted-route probe returned PNG for a prompt-only WebP request and WebP for explicit `imageGenerationOptions.outputFormat: "webp"`. Verify bytes rather than model claims. The standalone transparent endpoint still returns PNG; no format conversion is added. See `output/webp-prompt-probe-2026-09-29/verification.md` and `docs/solutions/architecture-patterns/qgrid-native-auto-image-generation-2.9.8.md`.
|
|
199
|
+
|
|
200
|
+
### Size hints and observed dimensions (2026-10-13)
|
|
201
|
+
|
|
202
|
+
`size` accepts `auto` or positive-integer WIDTHxHEIGHT hints. Keep the requested value separate from observed output metadata; do not promise exact dimensions or inject a resize. Prompt for the desired ratio. Twelve live subscription requests across hosted Responses and standalone generation/edit returned 841x1870 for 9:20, 941x1672 for 4K portrait, and 2172x724 for 4:1 (clamped to 3:1). Explicit 1152x2560 and even 1x1 hints returned 1254x1254 for a neutral prompt. These approximately 1.57MP observations are not a universal limit. Public API limits (16-pixel multiples, up to 4K) are not the private backend contract. Latest upstream Codex at `12cca49c81c8aa6a37a5cc4e39964ee3b70f9458` hardcodes `size: auto` for image generation/editing and saves bytes without resizing.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//#region src/utils/process-supervisor.ts
|
|
2
|
-
/**
|
|
3
|
-
* 프로세스 모니터링 도구 감지.
|
|
4
|
-
*
|
|
5
|
-
* 화면의 "재시작"은 스스로를 종료하는 것이다 — pm2 같은 도구가 죽은 프로세스를 다시
|
|
6
|
-
* 띄워주는 것이 곧 재시작이다. 그래서 그런 도구 없이 종료하면 그냥 서버가 내려간다.
|
|
7
|
-
* 로컬 개발에서 버튼을 누르면 아무도 살려주지 않으므로, 종료 전에 반드시 확인한다.
|
|
8
|
-
*
|
|
9
|
-
* 현재 배포 계약은 pm2 만 재시작을 보장한다. systemd 의 `INVOCATION_ID` 는 해당
|
|
10
|
-
* unit 이 `Restart=` 를 쓴다는 뜻이 아니므로 안전한 감독자 신호로 사용할 수 없다.
|
|
11
|
-
*
|
|
12
|
-
* 값이 아니라 존재 여부가 신호다. pm2 는 첫 프로세스에 `pm_id=0` 을 주므로 truthy 검사로
|
|
13
|
-
* 판별하면 놓친다.
|
|
14
|
-
*/
|
|
15
|
-
function detectSupervisor(env = process.env) {
|
|
16
|
-
if (env.pm_id !== void 0) return "pm2";
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
|
-
export { detectSupervisor };
|
|
21
|
-
|
|
22
|
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvY2Vzcy1zdXBlcnZpc29yLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9wcm9jZXNzLXN1cGVydmlzb3IudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiDtlITroZzshLjsiqQg66qo64uI7YSw66eBIOuPhOq1rCDqsJDsp4AuXG4gKlxuICog7ZmU66m07J2YIFwi7J6s7Iuc7J6RXCLsnYAg7Iqk7Iqk66Gc66W8IOyiheujjO2VmOuKlCDqsoPsnbTri6Qg4oCUIHBtMiDqsJnsnYAg64+E6rWs6rCAIOyjveydgCDtlITroZzshLjsiqTrpbwg64uk7IucXG4gKiDrnYTsm4zso7zripQg6rKD7J20IOqzpyDsnqzsi5zsnpHsnbTri6QuIOq3uOuemOyEnCDqt7jrn7Ag64+E6rWsIOyXhuydtCDsooXro4ztlZjrqbQg6re464OlIOyEnOuyhOqwgCDrgrTroKTqsITri6QuXG4gKiDroZzsu6wg6rCc67Cc7JeQ7IScIOuyhO2KvOydhCDriITrpbTrqbQg7JWE66y064+EIOyCtOugpOyjvOyngCDslYrsnLzrr4DroZwsIOyiheujjCDsoITsl5Ag67CY65Oc7IucIO2ZleyduO2VnOuLpC5cbiAqXG4gKiDtmITsnqwg67Cw7Y+sIOqzhOyVveydgCBwbTIg66eMIOyerOyLnOyekeydhCDrs7TsnqXtlZzri6QuIHN5c3RlbWQg7J2YIGBJTlZPQ0FUSU9OX0lEYCDripQg7ZW064u5XG4gKiB1bml0IOydtCBgUmVzdGFydD1gIOulvCDsk7Tri6TripQg65y77J20IOyVhOuLiOuvgOuhnCDslYjsoITtlZwg6rCQ64+F7J6QIOyLoO2YuOuhnCDsgqzsmqntlaAg7IiYIOyXhuuLpC5cbiAqXG4gKiDqsJLsnbQg7JWE64uI6528IOyhtOyerCDsl6zrtoDqsIAg7Iug7Zi464ukLiBwbTIg64qUIOyyqyDtlITroZzshLjsiqTsl5AgYHBtX2lkPTBgIOydhCDso7zrr4DroZwgdHJ1dGh5IOqygOyCrOuhnFxuICog7YyQ67OE7ZWY66m0IOuGk+y5nOuLpC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRldGVjdFN1cGVydmlzb3IoZW52OiBOb2RlSlMuUHJvY2Vzc0VudiA9IHByb2Nlc3MuZW52KTogXCJwbTJcIiB8IG51bGwge1xuICBpZiAoZW52LnBtX2lkICE9PSB1bmRlZmluZWQpIHJldHVybiBcInBtMlwiO1xuICByZXR1cm4gbnVsbDtcbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7QUFhQSxTQUFnQixpQkFBaUIsTUFBeUIsUUFBUSxLQUFtQjtDQUNuRixJQUFJLElBQUksVUFBVSxRQUFXLE9BQU87Q0FDcEMsT0FBTztBQUNUIn0=
|