@cartanova/qgrid-cli 2.10.2 → 2.10.4

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.
Files changed (118) hide show
  1. package/bundle/dist/application/monit/log-buffer.js +7 -11
  2. package/bundle/dist/application/monit/monit.frame.js +6 -11
  3. package/bundle/dist/application/qgrid/oauth.js +2 -2
  4. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +64 -141
  5. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +12 -36
  6. package/bundle/dist/application/qgrid/qgrid.frame.js +3 -6
  7. package/bundle/dist/application/qgrid/qgrid.types.js +2 -1
  8. package/bundle/dist/application/qgrid/token-subscriber.js +4 -7
  9. package/bundle/dist/application/qgrid/token-window-keepalive.js +3 -2
  10. package/bundle/dist/application/qgrid/tool-emulation-schema.js +4 -4
  11. package/bundle/dist/application/qgrid/tool-emulation.js +14 -2
  12. package/bundle/dist/application/request-log/request-log.model.js +7 -5
  13. package/bundle/dist/application/setting/setting.model.js +2 -64
  14. package/bundle/dist/application/setting/setting.store.js +3 -4
  15. package/bundle/dist/application/setting/setting.types.js +2 -12
  16. package/bundle/dist/i18n/en.js +1 -3
  17. package/bundle/dist/i18n/ko.js +1 -3
  18. package/bundle/dist/sonamu.config.js +6 -6
  19. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
  20. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +53 -84
  21. package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +55 -0
  22. package/bundle/dist/utils/providers/anthropic/claude-session.js +11 -13
  23. package/bundle/dist/utils/providers/common/effort.js +1 -1
  24. package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +3 -3
  25. package/bundle/dist/utils/providers/common/thinking.js +12 -0
  26. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +3 -2
  27. package/bundle/dist/utils/providers/openai/openai-direct-client.js +6 -16
  28. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +89 -78
  29. package/bundle/src/application/monit/log-buffer.test.ts +3 -4
  30. package/bundle/src/application/monit/log-buffer.ts +13 -27
  31. package/bundle/src/application/monit/monit.frame.test.ts +7 -8
  32. package/bundle/src/application/monit/monit.frame.ts +9 -14
  33. package/bundle/src/application/qgrid/oauth.ts +1 -1
  34. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +7 -111
  35. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +83 -179
  36. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +62 -1
  37. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +25 -71
  38. package/bundle/src/application/qgrid/qgrid.frame.test.ts +0 -38
  39. package/bundle/src/application/qgrid/qgrid.frame.ts +4 -12
  40. package/bundle/src/application/qgrid/qgrid.types.ts +2 -0
  41. package/bundle/src/application/qgrid/token-subscriber.test.ts +8 -18
  42. package/bundle/src/application/qgrid/token-subscriber.ts +10 -20
  43. package/bundle/src/application/qgrid/token-window-keepalive.test.ts +2 -2
  44. package/bundle/src/application/qgrid/token-window-keepalive.ts +2 -1
  45. package/bundle/src/application/qgrid/tool-emulation-schema.test.ts +6 -3
  46. package/bundle/src/application/qgrid/tool-emulation-schema.ts +3 -3
  47. package/bundle/src/application/qgrid/tool-emulation.test.ts +8 -1
  48. package/bundle/src/application/qgrid/tool-emulation.ts +29 -0
  49. package/bundle/src/application/request-log/request-log.model.ts +5 -5
  50. package/bundle/src/application/setting/setting.model.test.ts +1 -101
  51. package/bundle/src/application/setting/setting.model.ts +1 -74
  52. package/bundle/src/application/setting/setting.store.ts +2 -3
  53. package/bundle/src/application/setting/setting.types.ts +0 -12
  54. package/bundle/src/application/sonamu.generated.http +2 -7
  55. package/bundle/src/i18n/en.ts +0 -3
  56. package/bundle/src/i18n/ko.ts +0 -3
  57. package/bundle/src/sonamu.config.ts +5 -5
  58. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +3 -6
  59. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +26 -0
  60. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +70 -148
  61. package/bundle/src/utils/providers/anthropic/anthropic-thinking.test.ts +36 -0
  62. package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +84 -0
  63. package/bundle/src/utils/providers/anthropic/claude-session.test.ts +74 -13
  64. package/bundle/src/utils/providers/anthropic/claude-session.ts +11 -20
  65. package/bundle/src/utils/providers/common/effort.ts +1 -1
  66. package/bundle/src/utils/providers/common/provider-dispatcher.ts +4 -0
  67. package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -4
  68. package/bundle/src/utils/providers/common/thinking.ts +10 -0
  69. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +7 -3
  70. package/bundle/src/utils/providers/openai/openai-direct-client.test.ts +3 -3
  71. package/bundle/src/utils/providers/openai/openai-direct-client.ts +20 -33
  72. package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +97 -6
  73. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +113 -77
  74. package/bundle/web-dist/client/assets/index-CvG4UB9v.css +1 -0
  75. package/bundle/web-dist/client/assets/{index-DtRTSTes.js → index-reW8VvJF.js} +4 -4
  76. package/bundle/web-dist/client/assets/{logs-BhVYDEzC.js → logs-DDIt97D1.js} +1 -1
  77. package/bundle/web-dist/client/assets/{monit-BxN4JWqK.js → monit-DkABv7PG.js} +1 -1
  78. package/bundle/web-dist/client/assets/{routes-CBCqRp2o.js → routes-BRmQ4M3-.js} +1 -1
  79. package/bundle/web-dist/client/assets/services.generated-jnPHYMER.js +51 -0
  80. package/bundle/web-dist/client/assets/settings-BHvtycEZ.js +1 -0
  81. package/bundle/web-dist/client/assets/{show-BzYft5N4.js → show-qeTIzgDZ.js} +1 -1
  82. package/bundle/web-dist/client/assets/{tokens-PMybtI8X.js → tokens-5Bep_G-w.js} +1 -1
  83. package/bundle/web-dist/client/assets/{use-oauth-login-flow-C51_d6-6.js → use-oauth-login-flow-Cx-QO25M.js} +1 -1
  84. package/bundle/web-dist/client/assets/{use-update-token-mutation-D-Y8OySK.js → use-update-token-mutation-DQCwCYfR.js} +1 -1
  85. package/bundle/web-dist/client/index.html +3 -3
  86. package/bundle/web-dist/server/assets/{lazyRouteComponent-Bj1cPq6x.js → lazyRouteComponent-CUR-kWzI.js} +2 -2
  87. package/bundle/web-dist/server/assets/{logs-BSk9lOHe.js → logs-CagNGTBj.js} +3 -3
  88. package/bundle/web-dist/server/assets/{monit-D9W4ioUi.js → monit-DSxs4Jnl.js} +3 -3
  89. package/bundle/web-dist/server/assets/{plus-CmvbWuDf.js → plus-Bvdj8Pwx.js} +1 -1
  90. package/bundle/web-dist/server/assets/{react-dom-BjR1Iaz8.js → react-dom-CyuShs9K.js} +1 -1
  91. package/bundle/web-dist/server/assets/{refresh-cw-B8U9BL9-.js → refresh-cw-3QAiSWBd.js} +1 -1
  92. package/bundle/web-dist/server/assets/{routes-C7lB2Q85.js → routes-i-sTqXMX.js} +4 -4
  93. package/bundle/web-dist/server/assets/{services.generated-BM4X3Ecj.js → services.generated-FPwsG9p3.js} +116 -131
  94. package/bundle/web-dist/server/assets/{settings-BFVuldch.js → settings-DeHNwoMv.js} +40 -251
  95. package/bundle/web-dist/server/assets/{show-Cswh_tqs.js → show-C4jk1Hrp.js} +2 -2
  96. package/bundle/web-dist/server/assets/{tokens-DiTUwUwr.js → tokens-odgS2-9q.js} +4 -4
  97. package/bundle/web-dist/server/assets/{use-oauth-login-flow-DYKnG4A2.js → use-oauth-login-flow-DTQKdYBO.js} +1 -1
  98. package/bundle/web-dist/server/assets/{use-update-token-mutation-DqGMopII.js → use-update-token-mutation-xvXbPrCI.js} +1 -1
  99. package/bundle/web-dist/server/entry-server.generated.js +10 -10
  100. package/package.json +1 -1
  101. package/skills/qgrid/SKILL.md +1 -0
  102. package/skills/qgrid/references/ai-sdk-provider-contract.md +2 -1
  103. package/skills/qgrid/references/anthropic-claude-code-runtime.md +7 -4
  104. package/skills/qgrid/references/cli-env-and-server-boot.md +12 -9
  105. package/skills/qgrid/references/decision-rationale.md +9 -0
  106. package/skills/qgrid/references/openai-codex-runtime.md +6 -0
  107. package/skills/qgrid/references/token-auth-quota-lifecycle.md +2 -0
  108. package/skills/qgrid/references/tool-calling-and-multiturn.md +18 -1
  109. package/skills/qgrid/references/verification-and-debugging.md +1 -1
  110. package/bundle/dist/utils/process-supervisor.js +0 -22
  111. package/bundle/dist/utils/server-restart.js +0 -61
  112. package/bundle/src/utils/process-supervisor.test.ts +0 -24
  113. package/bundle/src/utils/process-supervisor.ts +0 -17
  114. package/bundle/src/utils/server-restart.test.ts +0 -76
  115. package/bundle/src/utils/server-restart.ts +0 -68
  116. package/bundle/web-dist/client/assets/index-D_sagzsR.css +0 -1
  117. package/bundle/web-dist/client/assets/services.generated-Cbvg2sGQ.js +0 -51
  118. package/bundle/web-dist/client/assets/settings-hBTEC7Ki.js +0 -1
@@ -1,7 +1,7 @@
1
- import { A as require_jsx_runtime, D as useQuery, T as isAxiosError, Z as __toESM, a as SettingService, j as require_react, k as useQueryClient, l as isSonamuError, n as QgridService, o as TokenService } from "./services.generated-BM4X3Ecj.js";
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-DqGMopII.js";
4
- import { t as lucidePlus } from "./plus-CmvbWuDf.js";
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
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
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)("span", {
623
- className: "text-[11px] text-sand-400",
624
- children: "기동 시점 값"
625
- })]
626
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
627
- className: "px-4 py-3 sm:px-5 grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1.5",
628
- children: runtime.map((info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
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
- groups.map((group) => {
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,5 +1,5 @@
1
- import { A as require_jsx_runtime, D as useQuery, J as __commonJSMin, Z as __toESM, i as RequestLogStepService, j as require_react, l as isSonamuError, r as RequestLogService } from "./services.generated-BM4X3Ecj.js";
2
- import { a as Link } from "./lazyRouteComponent-Bj1cPq6x.js";
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
4
  import { n as formatMicroUsd, t as cacheHitRate } from "./cost-CSH_t17I.js";
5
5
  import { t as requestModelDisplay } from "./request-model-CnCeaNLE.js";
@@ -1,8 +1,8 @@
1
- import { A as require_jsx_runtime, Z as __toESM, j as require_react, k as useQueryClient, n as QgridService, o as TokenService } from "./services.generated-BM4X3Ecj.js";
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-DqGMopII.js";
4
- import { n as OAuthCodeEntry, t as useOAuthLoginFlow } from "./use-oauth-login-flow-DYKnG4A2.js";
5
- import { t as lucidePlus } from "./plus-CmvbWuDf.js";
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 require_jsx_runtime, Z as __toESM, j as require_react, k as useQueryClient, n as QgridService, o as TokenService } from "./services.generated-BM4X3Ecj.js";
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 { E as useMutation, n as QgridService } from "./services.generated-BM4X3Ecj.js";
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 require_jsx_runtime, B as matchMutation, C as union, F as onlineManager, G as skipToken, H as noop$1, I as notifyManager, J as __commonJSMin, K as focusManager, L as functionalUpdate, M as Mutation, N as infiniteQueryBehavior, O as QueryClientProvider, P as Query, R as hashKey, S as string$2, U as partialMatchKey, V as matchQuery, W as resolveStaleTime, X as __require, Y as __exportAll, Z as __toESM, _ 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 require_react, m as _enum, n as QgridService, o as TokenService, p as setLocale, q as Subscribable, s as SonamuQueryMode, u as zArrayable, v as date, w as unknown, x as object, y as int, z as hashQueryKeyByOptions } from "./assets/services.generated-BM4X3Ecj.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-Bj1cPq6x.js";
3
- import { n as require_shim, t as require_react_dom } from "./assets/react-dom-BjR1Iaz8.js";
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.2";
46877
+ var qgridVersion = "2.10.4";
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-DiTUwUwr.js");
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-BFVuldch.js");
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-D9W4ioUi.js");
68763
+ var $$splitComponentImporter$3 = () => import("./assets/monit-DSxs4Jnl.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-BSk9lOHe.js");
69140
+ var $$splitComponentImporter$2 = () => import("./assets/logs-CagNGTBj.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-C7lB2Q85.js");
69154
+ var $$splitComponentImporter$1 = () => import("./assets/routes-i-sTqXMX.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-Cswh_tqs.js");
69158
+ var $$splitComponentImporter = () => import("./assets/show-C4jk1Hrp.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.2",
3
+ "version": "2.10.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cartanova-ai/qgrid"
@@ -45,6 +45,7 @@ Request logging is enabled by default. Use `providerOptions.qgrid.logger: false`
45
45
  ## Non-Negotiable Boundaries
46
46
 
47
47
  - Use `packages/ai-sdk` as the active public SDK surface.
48
+ - Expose thinking only as `providerOptions.qgrid.thinking?: boolean`: omission preserves the model policy, false overrides effort, and unsupported/unverified off requests fail explicitly. See the SDK and provider runtime references for the dated support snapshot. Keep Anthropic thinking controls separate from mandatory configuration and memory isolation. Haiku/Sonnet 5.5 off uses a computed thinking-only native body override with Claude Code >=2.1.295; never inherit or expose arbitrary extra body.
48
49
  - The old v1 SDK package (`packages/sdk`, `@cartanova/qgrid-sdk`) has been removed from the repository. It survives only as a deprecated npm artifact (1.9.0); do not resurrect it or write new code against it.
49
50
  - Let the server infer single-turn versus tool-run request-log lifecycle. Do not recreate caller-selected logging modes in the SDK or API examples.
50
51
  - Route provider models by prefix: `openai/*` goes to the OpenAI Codex runtime, `anthropic/*` goes to the Anthropic Claude Code runtime. Prefix-less model fallback is not implemented.
@@ -63,7 +63,8 @@ Do not write `providerOptions: { ... } satisfies QgridProviderOptions`: the expo
63
63
  rejected by the SDK before transport; omission keeps weighted round-robin routing.
64
64
  - `logger`: request-log switch. Omitted or `true` is the default and enables qgrid logging; `false` guarantees that generation creates zero qgrid request-log rows.
65
65
  - `sessionKey`: source for model-scoped opaque OpenAI prompt-cache affinity. Disabled for Anthropic models.
66
- - `effort`: provider-specific vocabulary. OpenAI (ChatGPT-subscription Codex route): `low | medium | high | xhigh | max | ultra`, where GPT-6 Astra, GPT-6 Sol, GPT-5.6 Sol, and Terra support `ultra`, and GPT-6 Luna and GPT-5.6 Luna support through `max`; the public OpenAI API's `none`/`minimal` do not exist on this route. Anthropic (Claude Code `--effort`): `low | medium | high | xhigh | max`. The server resolves the value per provider and model (`providers/common/effort.ts`): anything outside the provider set, or above the Codex model's supported maximum, is silently dropped and treated as unspecified — Anthropic falls back to qgrid's default `low`, OpenAI omits `reasoning` so the backend default applies. No error, no metadata; a debug log line only. Use `QgridOpenAIProviderOptions` / `QgridAnthropicProviderOptions` for provider-typed `satisfies`; `QgridProviderOptions` is their union.
66
+ - `thinking`: optional boolean for both providers. Omission preserves existing model behavior. True enables thinking with the normal request effort → config `defaultEffort` → default resolution; it does not guarantee thinking tokens on every response. False ignores both efforts, including in request logging, and rejects unsupported or unverified model/runtime combinations. Generate, stream, tool continuation, and structured output carry the same boolean. Update SDK and server together: older servers can ignore the field. No `defaultThinking` or public `effort: "none"` is exposed. Haiku/Sonnet 5.5 off requires Claude Code >=2.1.295; Sonnet uses `between_tools` and may retain thinking blocks between tools.
67
+ - `effort`: provider-specific vocabulary. OpenAI (ChatGPT-subscription Codex route): `low | medium | high | xhigh | max | ultra`, where GPT-6 Astra, GPT-6 Sol, GPT-5.6 Sol, and Terra support `ultra`, and GPT-6 Luna and GPT-5.6 Luna support through `max`; `none`/`minimal` are not public effort values; use `thinking: false` for verified off-capable models. Anthropic (Claude Code `--effort`): `low | medium | high | xhigh | max`. The server resolves the value per provider and model (`providers/common/effort.ts`): anything outside the provider set, or above the Codex model's supported maximum, is silently dropped and treated as unspecified. With thinking omitted, Anthropic falls back to qgrid's default `low`; OpenAI omits `reasoning.effort` so the backend default applies. With `thinking: true`, OpenAI explicitly falls back to `low` for absent or unsupported effort, including raw API requests. With `thinking: false`, both providers ignore caller/default effort and apply their off policy instead. Invalid effort itself produces no error or metadata, only a debug log; unsupported thinking-off requests still fail explicitly. Use `QgridOpenAIProviderOptions` / `QgridAnthropicProviderOptions` for provider-typed `satisfies`; `QgridProviderOptions` is their union.
67
68
  - `verbosity`: OpenAI/Codex route only.
68
69
  - `reasoningSummary`: OpenAI/Codex route only.
69
70
  - `serviceTier`: OpenAI/Codex route only.
@@ -77,7 +77,7 @@ claude -p
77
77
  --model <canonical-model-or-1m-suffix>
78
78
  --system-prompt <text> # small system prompt
79
79
  --system-prompt-file <path> # large system prompt
80
- --thinking disabled # omitted for Fable 5/5.1, Opus 5, Opus 5.5, Sonnet 5.5 and Haiku 5.5
80
+ --thinking <disabled|adaptive> # resolved from thinking and the existing model default
81
81
  --effort <effort-or-low>
82
82
  --disable-slash-commands
83
83
  --session-id <uuid>
@@ -97,7 +97,11 @@ Important details:
97
97
  - Large system prompts over 64 KiB are written to a temporary file to avoid argv `E2BIG` — this
98
98
  same branch absorbs large injected schema contracts, so the old 64 KiB schema argv limit no
99
99
  longer applies (only the global 512 KiB caller-schema complexity limit remains).
100
- - `--thinking disabled`, `MAX_THINKING_TOKENS=0`, and adaptive thinking env suppression keep thinking off for existing models. Fable 5, Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5 require always-on adaptive thinking (Fable: CLI catalog `rejects_disabled_thinking`; Opus 5.5: official spec, thinking cannot be turned off; Sonnet 5.5: API `{type: "disabled"}` returns 400 and the CLI catalog marks `rejects_disabled_thinking`; Haiku 5.5 (released 2026-10-07): the API accepts disabled thinking only at `high` effort or below and returns 400 at `xhigh`/`max`, and the Claude Code 2.1.293 catalog marks `rejects_disabled_thinking`). Opus 5 defaults to adaptive thinking and rejects disabled thinking at `xhigh`/`max` effort. qgrid omits all three suppressors for these models (`usesAdaptiveThinking`) and uses `effort` to control depth.
100
+ - One policy resolves the CLI thinking flag and effort before spawning. Omission preserves the existing model policy: Fable 5/5.1, Opus 5/5.5, Sonnet 5.5 and Haiku 5.5 keep adaptive defaults; other models use `--thinking disabled`. Explicit `true` uses `--thinking adaptive` and the normal effort resolution. Explicit `false` uses `--thinking disabled --effort low` only on supported models, ignoring caller/default effort. `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING` and `MAX_THINKING_TOKENS` are no longer sent. Do not reintroduce overlapping suppressors or inherit `CLAUDE_CODE_EFFORT_LEVEL`.
101
+ - The 2026-10-10 off policy includes Haiku 4.5/5.5, Sonnet 4.5/4.6/5/5.5, and Opus 4.5/4.6/4.7/4.8/5. Fable 5/5.1 and Opus 5.5 reject disabled thinking at the backend. Off requests for unverified models fail before spawning instead of changing models or silently enabling thinking.
102
+ - Haiku 5.5 and Sonnet 5.5 require Claude Code 2.1.295 or newer for explicit false. Cache the result of `claude --version`; unknown or older versions return an input error. The policy computes `CLAUDE_CODE_EXTRA_BODY` containing only `thinking: {type: "disabled"}` for Haiku or `thinking: {type: "between_tools"}` for Sonnet, alongside `--thinking disabled --effort low`. Native delivery was verified on 2.1.295; invalid-type and Sonnet-disabled negative controls confirmed that the CLI applied the body field. `between_tools` disables pre-answer thinking but may emit thinking blocks between tools. Never inherit this env variable or expose arbitrary body overrides to callers.
103
+ - Legacy environment suppression versus CLI-only disabled was compared on Sonnet 4.6 and Opus 4.8: both completed with zero thinking tokens in the short-text baseline. The comparison supports removing the duplicate suppressors without changing the omitted-option policy.
104
+
101
105
 
102
106
  ## Spawn env
103
107
 
@@ -109,14 +113,13 @@ Included env:
109
113
  - `TMPDIR`
110
114
  - `CLAUDE_CODE_OAUTH_TOKEN`
111
115
  - `CLAUDE_CONFIG_DIR`
116
+ - `CLAUDE_CODE_EXTRA_BODY` only when computed by the thinking policy for explicit Haiku/Sonnet 5.5 off; never inherited
112
117
  - `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`
113
- - `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` except for Fable 5/5.1 and Opus 5
114
118
  - `CLAUDE_CODE_DISABLE_CLAUDE_MDS=1`
115
119
  - `CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1`
116
120
  - `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS=1`
117
121
  - `CLAUDE_CODE_DISABLE_WORKFLOWS=1`
118
122
  - `CLAUDE_CODE_ATTRIBUTION_HEADER=0`
119
- - `MAX_THINKING_TOKENS=0` except for Fable 5/5.1 and Opus 5
120
123
  - `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` when model does not support qgrid's 1M path
121
124
  - `MAX_STRUCTURED_OUTPUT_RETRIES` for streaming structured output only (not non-streaming `generate`)
122
125
 
@@ -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
- ### Restart from the dashboard
95
+ ### Process lifecycle
96
96
 
97
- The Settings page can restart the server. There is no `pm2 restart` call — the process exits and the supervisor respawns it, which is what "restart" means here.
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.
@@ -37,6 +37,15 @@ Key decisions:
37
37
  - The SDK should stay a thin adapter. Server APIs own request-log lifecycle, provider dispatch, structured-output emulation, and provider runtime details.
38
38
  - Request logging defaults to enabled. `providerOptions.qgrid.logger: false` is the single per-generation opt-out for both native qgrid logs and `createQgridLogger` telemetry logs; it must not alter generation or tool behavior.
39
39
 
40
+ ## Thinking Control
41
+
42
+ Source: `docs/plans/2026-10-10-001-feat-thinking-control-plan.md`.
43
+
44
+ - Use the independent `providerOptions.qgrid.thinking?: boolean` switch. False makes effort irrelevant; do not reject the combination or expose a cross-provider `effort: "none"` value.
45
+ - Preserve omitted-option behavior. Reject unsupported and runtime-unverified off requests before token acquisition/spawn, without fallback. Backend capability and Claude Code delivery are separate evidence.
46
+ - Use one Anthropic CLI thinking policy instead of overlapping environment suppressors. Preserve the OAuth env allowlist, token-specific config, empty settings, isolated cwd, memory/CLAUDE.md/skills/workflow disabling, tools, attribution, and 1M policy. These isolation controls prevent previously observed cross-request context injection and account metadata leakage.
47
+ - Haiku/Sonnet 5.5 use a narrowly computed native `CLAUDE_CODE_EXTRA_BODY` thinking field, guarded by Claude Code >=2.1.295. Do not inherit that variable, expose arbitrary body overrides, add a transport, or add a relay. Sonnet's `between_tools` exception remains visible: it turns off pre-answer thinking but can retain thinking blocks between tools.
48
+
40
49
  ## Tool Calling And Request-Log Lifecycle
41
50
 
42
51
  Sources:
@@ -12,6 +12,12 @@ Use this reference before changing OpenAI transport, concurrency, routing, promp
12
12
  - Direct quota lookup: `openai-quota.ts`.
13
13
  - Provider integration and history: `packages/api/src/application/qgrid/qgrid.dispatcher.ts`, `conv-routing.ts`.
14
14
 
15
+ ## Thinking control
16
+
17
+ `providerOptions.qgrid.thinking` reaches the Responses builder as a boolean. False overrides request/default effort and sends `reasoning: { effort: "none" }` without a reasoning summary. True uses a valid resolved effort, or explicitly sends `low` when effort is absent or unsupported, so a raw API request cannot fall back to a non-thinking backend default. Omission preserves the existing behavior: valid effort is forwarded; absent or unsupported effort leaves the backend default in effect. True and omission retain the requested reasoning summary policy. `none` belongs only to the internal wire type, not the public effort enum.
18
+
19
+ The 2026-10-10 subscription probe accepted off for `gpt-6-sol`, `gpt-6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, and `gpt-5.5`. `gpt-6-astra` and `gpt-6.1-sol` explicitly rejected it. Other model IDs are unverified for off and are rejected locally only when false is requested. Model-access failures are not evidence of mandatory thinking. Validate terminal success and response settings, not HTTP 200 alone: errors can arrive within SSE. The same policy applies to text-model requests driving image generation, not to the image model itself.
20
+
15
21
  ## Adding an OpenAI model
16
22
 
17
23
  Use this checklist whenever a model is added. A public API release does not by itself prove
@@ -253,6 +253,8 @@ Anthropic's 5-hour usage window anchors on the first request, so an unused token
253
253
 
254
254
  Keepalive breaks that coupling by burning the window clock during idle time. Every keepalive-enabled active Anthropic token gets one cheap request as soon as its window expires, so windows always run and a workday lands mid-window rather than on a boundary. Across an eight-hour day this raises the number of windows the day can touch from a fixed 2 to an expected 2.6.
255
255
 
256
+ The keepalive request uses `anthropic/claude-haiku-5-5`, `thinking: false`, `effort: "low"`, and the prompt `Reply OK.`. Thinking is disabled because this request only opens the usage window; the false policy overrides the supplied effort and uses the provider's low-effort off path. Haiku 5.5 off requires Claude Code >=2.1.295. The model constant lives in `token-window-keepalive.ts`; changing it does not change the scheduler or opt-in gates below.
257
+
256
258
  - Since 2.8.0 (`ea7feb3`) keepalive is opt-in at two levels. The runner instance must have env `QGRID_TOKEN_WINDOW_KEEPALIVE_ENABLED=true` (default off; set it on one instance per shared DB), and a token is a target only when `tokens.keepalive_enabled` is `true` (default `false`, toggled in the dashboard settings panel). The global setting `qgrid.tokenWindowKeepaliveEnabled` is a kill switch (`false` disables everything), not a substitute for either gate. In 2.7.6 every instance fired for every active token.
257
259
  - Operational trap: the 2.8.0 migration adds `keepalive_enabled` as default `false` without backfilling existing tokens. After upgrading, both the runner env and the per-token toggles must be set, or keepalive silently schedules zero tokens. This happened on dev0 after the 2026-08-31 self-update; a lingering 2.7.6 instance masked it until 2026-09-01. Diagnose with `GET /api/setting/listSettings` (`keepaliveRunnerEnabled`), `GET /api/token/findMany?subset=A` (`keepalive_enabled`), and the latest request log under project `qgrid-token-window-keepalive`.
258
260
  - The next fire is scheduled from the token's real `resetsAt` rather than a fixed five-hour timer, so a provider-side change to the window length is followed automatically.
@@ -24,6 +24,16 @@ qgrid supports the AI SDK `tools` interface, but it does not use native provider
24
24
  - qgrid maps that structured object back into AI SDK `tool-call` content.
25
25
  - The AI SDK executes the actual tool functions and calls qgrid again with tool results.
26
26
 
27
+ OpenAI can emit separate commentary and final-answer message items in one response.
28
+ Plain commentary is an intermediate update, not an emulation payload: do not join
29
+ it to the final JSON. Known final-answer message deltas still stream incrementally.
30
+ For tool emulation, a complete message that passes the strict envelope and tool-name
31
+ validation with `action: tool_call` is the client handoff boundary. Stop aggregating
32
+ and streaming later text from that response: it cannot incorporate a tool result
33
+ that the client has not produced yet. Drain the upstream response to preserve usage,
34
+ errors, and cancellation. Never split concatenated JSON inside a single message or
35
+ recover malformed envelopes. Ordinary text requests keep their existing aggregation.
36
+
27
37
  `generateText` and `streamText` default to `stepCountIs(1)`. When an executable
28
38
  tool can run before the final answer, callers must set a bounded `stopWhen`
29
39
  (for example `stepCountIs(3)`) or the AI SDK stops after the first tool-call
@@ -99,9 +109,16 @@ The schema explicitly tells the model:
99
109
 
100
110
  - use `tool_call` when client-side tool execution is needed;
101
111
  - use `answer` only for a final answer;
102
- - do not invoke listed tools as native Claude Code tools;
112
+ - return the tool request as the JSON response body, then end the turn and wait for client results;
113
+ - do not invent tool results or emit an answer before the required results arrive;
103
114
  - put tool arguments in `args` as a JSON string.
104
115
 
116
+ Do not instruct OpenAI to use a `StructuredOutput` or Claude Code tool: neither is
117
+ provided on this route. The legacy schema descriptions contained that stale
118
+ instruction; schema descriptions now explain the actual client handoff protocol.
119
+ The discriminated union, required fields, argument encoding and user-schema refs
120
+ are unchanged. Prompt wording improves guidance but does not guarantee model compliance.
121
+
105
122
  Delivery differs by provider (SON-532). OpenAI: `qgrid.dispatcher.ts` strictifies the composed
106
123
  envelope through `buildStrictOutputSchema` and passes it as the response format — constrained
107
124
  decoding enforces it. Anthropic: no schema is passed to CC at all; `schema-prompt.ts` renders the