cli-jaw 2.2.19 → 2.2.20

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 (95) hide show
  1. package/README.md +94 -3
  2. package/dist/server.js +11 -0
  3. package/dist/server.js.map +1 -1
  4. package/dist/src/agent/spawn/process-kill.js +96 -2
  5. package/dist/src/agent/spawn/process-kill.js.map +1 -1
  6. package/dist/src/agent/spawn.js +34 -65
  7. package/dist/src/agent/spawn.js.map +1 -1
  8. package/dist/src/bgtask/runner.js +26 -21
  9. package/dist/src/bgtask/runner.js.map +1 -1
  10. package/dist/src/cli/acp-client.js +11 -5
  11. package/dist/src/cli/acp-client.js.map +1 -1
  12. package/dist/src/cli/employee-handler.js +3 -2
  13. package/dist/src/cli/employee-handler.js.map +1 -1
  14. package/dist/src/code-mode/acp-host.js +22 -10
  15. package/dist/src/code-mode/acp-host.js.map +1 -1
  16. package/dist/src/core/config.js +14 -0
  17. package/dist/src/core/config.js.map +1 -1
  18. package/dist/src/core/db.js +16 -0
  19. package/dist/src/core/db.js.map +1 -1
  20. package/dist/src/core/employees.js +22 -4
  21. package/dist/src/core/employees.js.map +1 -1
  22. package/dist/src/core/windows-shell.js +63 -0
  23. package/dist/src/core/windows-shell.js.map +1 -0
  24. package/dist/src/discord/bot.js +199 -147
  25. package/dist/src/discord/bot.js.map +1 -1
  26. package/dist/src/discord/gateway-supervisor.js +303 -0
  27. package/dist/src/discord/gateway-supervisor.js.map +1 -0
  28. package/dist/src/discord/rest-scheduler.js +448 -0
  29. package/dist/src/discord/rest-scheduler.js.map +1 -0
  30. package/dist/src/discord/send-only-client.js +59 -43
  31. package/dist/src/discord/send-only-client.js.map +1 -1
  32. package/dist/src/manager/lifecycle.js +7 -2
  33. package/dist/src/manager/lifecycle.js.map +1 -1
  34. package/dist/src/manager/notes/capabilities.js +9 -7
  35. package/dist/src/manager/notes/capabilities.js.map +1 -1
  36. package/dist/src/memory/bootstrap.js +5 -1
  37. package/dist/src/memory/bootstrap.js.map +1 -1
  38. package/dist/src/memory/host-toolchain.js +156 -0
  39. package/dist/src/memory/host-toolchain.js.map +1 -0
  40. package/dist/src/messaging/channel-capabilities.js +30 -0
  41. package/dist/src/messaging/channel-capabilities.js.map +1 -0
  42. package/dist/src/messaging/delivery-outcome.js +73 -0
  43. package/dist/src/messaging/delivery-outcome.js.map +1 -0
  44. package/dist/src/messaging/draft-stream.js +154 -0
  45. package/dist/src/messaging/draft-stream.js.map +1 -0
  46. package/dist/src/messaging/runtime.js +3 -9
  47. package/dist/src/messaging/runtime.js.map +1 -1
  48. package/dist/src/messaging/send.js +40 -8
  49. package/dist/src/messaging/send.js.map +1 -1
  50. package/dist/src/messaging/types.js +22 -1
  51. package/dist/src/messaging/types.js.map +1 -1
  52. package/dist/src/notes/search.js +6 -4
  53. package/dist/src/notes/search.js.map +1 -1
  54. package/dist/src/prompt/builder.js +110 -6
  55. package/dist/src/prompt/builder.js.map +1 -1
  56. package/dist/src/prompt/conversation-context.js +23 -0
  57. package/dist/src/prompt/conversation-context.js.map +1 -0
  58. package/dist/src/prompt/templates/a1-system.md +28 -12
  59. package/dist/src/prompt/templates/control-system.md +19 -6
  60. package/dist/src/prompt/templates/employee.md +16 -3
  61. package/dist/src/security/path-guards.js +124 -27
  62. package/dist/src/security/path-guards.js.map +1 -1
  63. package/dist/src/slack/api.js +38 -5
  64. package/dist/src/slack/api.js.map +1 -1
  65. package/dist/src/slack/bot.js +214 -35
  66. package/dist/src/slack/bot.js.map +1 -1
  67. package/dist/src/slack/context.js +192 -0
  68. package/dist/src/slack/context.js.map +1 -0
  69. package/dist/src/slack/conversation.js +275 -0
  70. package/dist/src/slack/conversation.js.map +1 -0
  71. package/dist/src/slack/enrichment-cache.js +334 -0
  72. package/dist/src/slack/enrichment-cache.js.map +1 -0
  73. package/dist/src/slack/history.js +47 -8
  74. package/dist/src/slack/history.js.map +1 -1
  75. package/dist/src/slack/identity.js +99 -190
  76. package/dist/src/slack/identity.js.map +1 -1
  77. package/dist/src/slack/ingress.js +78 -2
  78. package/dist/src/slack/ingress.js.map +1 -1
  79. package/dist/src/slack/progress.js +39 -51
  80. package/dist/src/slack/progress.js.map +1 -1
  81. package/dist/src/slack/thread-tracker.js +78 -0
  82. package/dist/src/slack/thread-tracker.js.map +1 -1
  83. package/dist/src/telegram/bot.js +105 -56
  84. package/dist/src/telegram/bot.js.map +1 -1
  85. package/dist/src/telegram/update-offset.js +152 -0
  86. package/dist/src/telegram/update-offset.js.map +1 -0
  87. package/package.json +1 -1
  88. package/public/dist/assets/Agent-C5EvX-_v.js +1 -0
  89. package/public/dist/assets/{manager-Bjuh1jpq.js → manager-CP-Au860.js} +1 -1
  90. package/public/dist/manager/index.html +1 -1
  91. package/public/manager/src/settings/cli-status-polling.ts +84 -0
  92. package/public/manager/src/settings/pages/Agent.tsx +84 -5
  93. package/scripts/install-officecli.ps1 +38 -2
  94. package/scripts/install.ps1 +1 -1
  95. package/public/dist/assets/Agent-nMPZsMfN.js +0 -1
@@ -6,7 +6,7 @@
6
6
  <link rel="icon" type="image/png" href="/dist/assets/icon-192-CwXPIYF5.png">
7
7
  <title>Jaw Manager</title>
8
8
  <script src="/manager/theme-boot.js"></script>
9
- <script type="module" crossorigin src="/dist/assets/manager-Bjuh1jpq.js"></script>
9
+ <script type="module" crossorigin src="/dist/assets/manager-CP-Au860.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/dist/assets/vendor-render-Bjnw0wQ6.css">
11
11
  <link rel="stylesheet" crossorigin href="/dist/assets/manager-LkGs6qEJ.css">
12
12
  </head>
@@ -0,0 +1,84 @@
1
+ // #312: the Settings panel used to read /api/cli-status once, so a probe that
2
+ // was still running when the page mounted left "상태 확인 중" on screen forever.
3
+ //
4
+ // The server cannot fix this on its own: CliStatusCache is demand-driven and
5
+ // has no timer (src/cli/cli-status.ts), so a snapshot only advances when
6
+ // somebody reads it again. The UI has to be that somebody — but bounded, since
7
+ // a read can fork a worker that runs real CLI probes.
8
+
9
+ export type CliStatusProbeState = 'checking' | 'fresh' | 'stale' | 'failing';
10
+
11
+ export type PollableCliStatus = {
12
+ probeState?: CliStatusProbeState;
13
+ /** Server-provided backoff deadline, present while probeState is `failing`. */
14
+ nextRetryAt?: number;
15
+ };
16
+
17
+ /** Floor for the first delay: a GET can fork a worker running real CLI probes. */
18
+ export const CLI_STATUS_MIN_DELAY_MS = 1_000;
19
+ export const CLI_STATUS_MAX_DELAY_MS = 8_000;
20
+
21
+ /**
22
+ * The worker itself may legitimately run for 60s
23
+ * (WORKER_OUTER_TIMEOUT_MS in src/cli/cli-status-worker.ts). Give it that plus
24
+ * room for one observing read, or a healthy slow host gets reported as a
25
+ * timeout.
26
+ */
27
+ export const CLI_STATUS_POLL_HORIZON_MS = 90_000;
28
+ export const CLI_STATUS_MAX_ATTEMPTS = 24;
29
+
30
+ /**
31
+ * `failing` is deliberately NOT terminal. The cache restarts probing on the
32
+ * first read after its backoff expires, so a UI that stops on `failing` would
33
+ * simply swap one permanent notice for another and never observe the recovery.
34
+ */
35
+ export function shouldPollCliStatus(
36
+ snapshot: Record<string, PollableCliStatus> | null | undefined,
37
+ cli: string | null | undefined,
38
+ ): boolean {
39
+ if (!snapshot || !cli) return false;
40
+ const state = snapshot[cli]?.probeState;
41
+ if (!state) return false;
42
+ return state === 'checking' || state === 'failing';
43
+ }
44
+
45
+ /** Gentle backoff between the floor and the ceiling. */
46
+ export function nextCliStatusPollDelay(attempt: number): number {
47
+ const step = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
48
+ const delay = CLI_STATUS_MIN_DELAY_MS * 2 ** Math.min(step, 8);
49
+ return Math.min(Math.max(delay, CLI_STATUS_MIN_DELAY_MS), CLI_STATUS_MAX_DELAY_MS);
50
+ }
51
+
52
+ export type PollSchedule =
53
+ | { kind: 'stop' }
54
+ | { kind: 'exhausted' }
55
+ | { kind: 'wait'; delayMs: number };
56
+
57
+ /**
58
+ * Decides the next move. Two independent bounds, neither of which resets on
59
+ * server responses: a wall-clock deadline and a cap on real requests. Waiting
60
+ * out a server backoff must NOT consume an attempt, or a host that is merely
61
+ * backing off would be declared timed-out without ever being asked again.
62
+ */
63
+ export function planCliStatusPoll(input: {
64
+ snapshot: Record<string, PollableCliStatus> | null | undefined;
65
+ cli: string | null | undefined;
66
+ attempts: number;
67
+ now: number;
68
+ deadline: number;
69
+ }): PollSchedule {
70
+ const { snapshot, cli, attempts, now, deadline } = input;
71
+ if (!shouldPollCliStatus(snapshot, cli)) return { kind: 'stop' };
72
+ if (now >= deadline) return { kind: 'exhausted' };
73
+ if (attempts >= CLI_STATUS_MAX_ATTEMPTS) return { kind: 'exhausted' };
74
+
75
+ const backoff = snapshot?.[cli!]?.nextRetryAt;
76
+ const earliest = typeof backoff === 'number' && backoff > now
77
+ ? backoff
78
+ : now + nextCliStatusPollDelay(attempts);
79
+ // A backoff reaching past the deadline means the answer will not arrive in
80
+ // time: wait until the deadline and report exhaustion there rather than
81
+ // firing a doomed request or arming an unbounded timer.
82
+ const target = Math.min(earliest, deadline);
83
+ return { kind: 'wait', delayMs: Math.max(target - now, 0) };
84
+ }
@@ -1,5 +1,9 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import type { SettingsPageProps, DirtyEntry } from '../types';
3
+ import {
4
+ CLI_STATUS_POLL_HORIZON_MS,
5
+ planCliStatusPoll,
6
+ } from '../cli-status-polling';
3
7
  import {
4
8
  PageError,
5
9
  PageLoading,
@@ -59,6 +63,8 @@ type CliStatusInfo = {
59
63
  capabilityReady: boolean | null;
60
64
  probeState: 'checking' | 'fresh' | 'stale' | 'failing';
61
65
  probeError?: string;
66
+ /** Server backoff deadline; used to time re-reads while `failing`. */
67
+ nextRetryAt?: number;
62
68
  };
63
69
 
64
70
  export function conflictSettingsFromError(error: unknown): AgentSnapshot | null {
@@ -123,9 +129,24 @@ export default function Agent({ port, client, dirty, registerSave }: SettingsPag
123
129
  }
124
130
  }, [client]);
125
131
 
126
- const loadCliStatus = useCallback(async () => {
127
- try { setCliStatus(await client.get<Record<string, CliStatusInfo>>('/api/cli-status')); }
128
- catch { setCliStatus({}); }
132
+ // Generation ref, same convention as Browser.tsx: clearing a timer does not
133
+ // stop a request that is already in flight, and that response would
134
+ // otherwise setState after unmount or after the user switched CLI.
135
+ const cliStatusGenRef = useRef(0);
136
+ const cliStatusRef = useRef<Record<string, CliStatusInfo>>({});
137
+ const [cliStatusExhausted, setCliStatusExhausted] = useState(false);
138
+
139
+ const loadCliStatus = useCallback(async (gen?: number) => {
140
+ try {
141
+ const next = await client.get<Record<string, CliStatusInfo>>('/api/cli-status');
142
+ if (gen !== undefined && gen !== cliStatusGenRef.current) return;
143
+ cliStatusRef.current = next;
144
+ setCliStatus(next);
145
+ } catch {
146
+ if (gen !== undefined && gen !== cliStatusGenRef.current) return;
147
+ cliStatusRef.current = {};
148
+ setCliStatus({});
149
+ }
129
150
  }, [client]);
130
151
 
131
152
  const loadFlush = useCallback(async () => {
@@ -165,6 +186,57 @@ export default function Agent({ port, client, dirty, registerSave }: SettingsPag
165
186
  void loadEmployees();
166
187
  }, [loadCliMeta, loadCliStatus, loadEmployees, loadFlush]);
167
188
 
189
+ // #312: the server never pushes — CliStatusCache is demand-driven and has
190
+ // no timer — so a probe still running at mount would leave the notice up
191
+ // forever unless we ask again. Bounded by a wall-clock horizon AND a
192
+ // request cap, neither of which resets on server responses.
193
+ useEffect(() => {
194
+ if (!draft.cli) return;
195
+ const gen = cliStatusGenRef.current + 1;
196
+ cliStatusGenRef.current = gen;
197
+ setCliStatusExhausted(false);
198
+
199
+ const deadline = Date.now() + CLI_STATUS_POLL_HORIZON_MS;
200
+ let attempts = 0;
201
+ let timer: ReturnType<typeof setTimeout> | undefined;
202
+
203
+ const tick = () => {
204
+ if (gen !== cliStatusGenRef.current) return;
205
+ const plan = planCliStatusPoll({
206
+ snapshot: cliStatusRef.current,
207
+ cli: draft.cli,
208
+ attempts,
209
+ now: Date.now(),
210
+ deadline,
211
+ });
212
+ if (plan.kind === 'stop') return;
213
+ if (plan.kind === 'exhausted') {
214
+ setCliStatusExhausted(true);
215
+ return;
216
+ }
217
+ timer = setTimeout(() => {
218
+ if (gen !== cliStatusGenRef.current) return;
219
+ // Only a real request consumes the cap; waiting out a server
220
+ // backoff must not burn attempts.
221
+ attempts += 1;
222
+ void loadCliStatus(gen).then(() => {
223
+ if (gen === cliStatusGenRef.current) tick();
224
+ });
225
+ }, plan.delayMs);
226
+ };
227
+ tick();
228
+
229
+ return () => {
230
+ // Poison in-flight responses for this generation, then stop the timer.
231
+ cliStatusGenRef.current = gen + 1;
232
+ if (timer !== undefined) clearTimeout(timer);
233
+ };
234
+ // NOTE: cliStatus is deliberately NOT a dependency. Re-running this
235
+ // effect on every response would reset the deadline and the attempt
236
+ // counter, making both bounds unbounded in practice. The latest
237
+ // snapshot is read through a ref instead.
238
+ }, [draft.cli, loadCliStatus]);
239
+
168
240
  useEffect(() => {
169
241
  if (state.kind !== 'ready') return;
170
242
  const cliKeys = Object.keys(state.data.perCli || {});
@@ -331,9 +403,16 @@ export default function Agent({ port, client, dirty, registerSave }: SettingsPag
331
403
  {sessionMigrationError ? <span className="settings-field-error" role="alert">{sessionMigrationError}</span> : null}
332
404
  </div>
333
405
  ) : null}
334
- {cliStatus[draft.cli]?.probeState === 'checking' ? (
406
+ {cliStatus[draft.cli]?.probeState === 'checking' && !cliStatusExhausted ? (
335
407
  <div className="settings-inline-notice" role="status" aria-live="polite">상태 확인 중</div>
336
408
  ) : null}
409
+ {cliStatusExhausted ? (
410
+ // Without this, a poll that runs out while the snapshot still
411
+ // says `checking` would leave the exact notice #312 reported.
412
+ <div className="settings-inline-notice" role="alert">
413
+ 상태 확인이 끝나지 않았습니다. 새로고침하거나 잠시 후 다시 확인하세요.
414
+ </div>
415
+ ) : null}
337
416
  {cliStatus[draft.cli]?.probeState === 'failing' ? (
338
417
  // Without this the panel stays silent while every probe errors,
339
418
  // which is the state #277 reported as an endless "stale".
@@ -1,4 +1,4 @@
1
- param(
1
+ param(
2
2
  [switch]$Force,
3
3
  [switch]$Update,
4
4
  [switch]$Upstream,
@@ -40,11 +40,46 @@ function Normalize-Version([string]$version) {
40
40
  return $version.Trim().TrimStart('v')
41
41
  }
42
42
 
43
+ function Get-LatestRelease([string]$repoName) {
44
+ return Invoke-RestMethod -Uri "https://api.github.com/repos/$repoName/releases/latest" -Headers @{ "User-Agent" = "cli-jaw-postinstall" }
45
+ }
46
+
43
47
  function Get-LatestTag([string]$repoName) {
44
- $release = Invoke-RestMethod -Uri "https://api.github.com/repos/$repoName/releases/latest" -Headers @{ "User-Agent" = "cli-jaw-postinstall" }
48
+ $release = Get-LatestRelease $repoName
45
49
  return [string]$release.tag_name
46
50
  }
47
51
 
52
+ function Assert-AssetPublished([string]$repoName, [string]$assetName) {
53
+ try {
54
+ $release = Get-LatestRelease $repoName
55
+ } catch {
56
+ # Match the shell installer's network fallback: if GitHub metadata itself is
57
+ # unavailable, let the download report the concrete transport failure.
58
+ Write-Warn "Could not inspect $repoName release assets; the download will verify availability"
59
+ return
60
+ }
61
+
62
+ $published = @($release.assets | ForEach-Object { [string]$_.name } | Where-Object { $_ })
63
+ if ($published -contains $assetName) { return }
64
+
65
+ $tag = if ($release.tag_name) { [string]$release.tag_name } else { "unknown" }
66
+ $publishedText = if ($published.Count -gt 0) { $published -join " " } else { "(none)" }
67
+ $lines = @(
68
+ "$repoName latest release ($tag) has no $assetName.",
69
+ " published: $publishedText"
70
+ )
71
+ if ($repoName -eq "lidge-jun/OfficeCLI") {
72
+ $lines += @(
73
+ "",
74
+ " The supported fork currently publishes only officecli-mac-arm64.",
75
+ " For general XLSX/accessibility work use upstream, which builds every platform:",
76
+ " powershell -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Upstream",
77
+ " The fork is required only for CJK font handling and HWP (rhwp sidecars)."
78
+ )
79
+ }
80
+ Fail ($lines -join [Environment]::NewLine)
81
+ }
82
+
48
83
  Write-Info "Platform: win32/$arch -> $asset"
49
84
 
50
85
  if ((Test-Path $targetBin) -and -not $Force -and -not $Update) {
@@ -85,6 +120,7 @@ if ((Test-Path $targetBin) -and -not $Force -and $Update) {
85
120
  }
86
121
  }
87
122
 
123
+ Assert-AssetPublished $Repo $asset
88
124
  New-Item -ItemType Directory -Force -Path $installDir | Out-Null
89
125
  $downloadUrl = "https://github.com/$Repo/releases/latest/download/$asset"
90
126
 
@@ -1,4 +1,4 @@
1
- #Requires -Version 5.1
1
+ #Requires -Version 5.1
2
2
  <#
3
3
  .SYNOPSIS
4
4
  CLI-JAW one-click installer for native Windows (beta).
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-XQCOJYun.js";import{n as t,t as n}from"./jsx-runtime-gFMgZ0TQ.js";import{t as r}from"./settings-client-CjbBShxm.js";import{t as i}from"./error-normalize-BzeO0SXE.js";import{a,i as o,n as s,r as c,t as l}from"./page-shell-LF7vVVfG.js";import{i as u,s as d}from"./fields-zkeByFQW.js";import{a as f,n as p,o as m}from"./Permissions-CnKt44dN.js";import{a as ee,c as h,i as g,l as _,n as v,o as y,r as b,s as x,t as te}from"./agent-meta-Y3DE6sxh.js";import{t as S}from"./path-utils-LsJeDCKF.js";var C=e(t(),1),w=n();function ne({cli:e,cliOptions:t,provider:n=``,providerOptions:r=[],model:i,modelOptions:a,effort:s,effortOptions:c,workingDir:l,workingDirError:f,cliMeta:p,onCliChange:m,onProviderChange:ee,onModelChange:h,onEffortChange:_,onWorkingDirChange:v}){let y=x(t),b=y.filter(e=>te.includes(e)).length;return(0,w.jsx)(o,{title:`Agent runtime`,hint:`Active CLI, model, effort, and workspace used by this instance.`,children:(0,w.jsxs)(`div`,{className:`settings-agent-runtime-grid`,children:[(0,w.jsx)(u,{id:`agent-cli`,label:`Active CLI`,value:e,options:y.map(e=>({value:e,label:g(e,p).label||e})),collapsedAfter:b,onChange:m}),r.length>0?(0,w.jsx)(u,{id:`agent-${e}-provider`,label:`Provider`,value:n,options:r.map(e=>({value:e,label:e})),onChange:e=>ee?.(e)}):null,(0,w.jsx)(u,{id:`agent-model`,label:`Active model`,value:i,options:a.length>0?a:[{value:``,label:`(default)`}],onChange:h}),(0,w.jsx)(u,{id:`agent-effort`,label:`Effort`,value:s,options:[{value:``,label:`(default)`},...c.map(e=>({value:e,label:e}))],disabled:c.length===0,onChange:_}),(0,w.jsx)(d,{id:`agent-workingDir`,label:`Working directory`,value:l,error:f,placeholder:`/path/to/project`,onChange:v})]})})}var T=[{value:`auto`,label:`Auto`},{value:`custom`,label:`Custom allowlist`}];function re({value:e,onChange:t}){let n=f(e),r=n.mode===`custom`?`custom`:`auto`,i=n.mode===`custom`?n.tokens:[],a=n.mode===`custom`?`${i.length} explicit token${i.length===1?``:`s`}`:`Runtime resolves allowed capabilities`;return(0,w.jsxs)(o,{title:`Permissions`,hint:`Quick runtime mode. Open Permissions for detailed allowlist editing.`,children:[(0,w.jsx)(u,{id:`agent-permissions-mode`,label:`Mode`,value:r,options:T,onChange:e=>{t(e===`auto`?`auto`:i.length>0&&p(i)?i:m(null))}}),(0,w.jsx)(`p`,{className:`settings-agent-note`,children:a})]})}function ie({activeCli:e,flushCli:t,flushModel:n,cliOptions:r,modelOptions:i,cliMeta:a,loading:s,error:c,onFlushCliChange:l,onFlushModelChange:d}){let f=t||e,p=f?g(f,a).label||f:`active`;return(0,w.jsx)(o,{title:`Flush Agent`,hint:`Separate summary agent used for context flush and compact work.`,children:(0,w.jsxs)(`details`,{className:`settings-agent-flush`,children:[(0,w.jsxs)(`summary`,{children:[(0,w.jsx)(`span`,{children:`Flush runtime`}),(0,w.jsxs)(`code`,{children:[p,n&&n!==`default`?` / ${n}`:``]})]}),s?(0,w.jsx)(`p`,{className:`settings-agent-note`,children:`Loading flush settings...`}):null,c?(0,w.jsx)(`p`,{className:`settings-field-error`,role:`alert`,children:c}):null,(0,w.jsxs)(`div`,{className:`settings-agent-runtime-grid`,children:[(0,w.jsx)(u,{id:`agent-flush-cli`,label:`Flush CLI`,value:t,options:[{value:``,label:`(active CLI)`},...r.map(e=>({value:e,label:g(e,a).label||e}))],onChange:l}),(0,w.jsx)(u,{id:`agent-flush-model`,label:`Flush model`,value:n,options:[{value:``,label:`(default)`},...i],onChange:d})]})]})})}function E(e){return e.source===`static`||e.id.startsWith(`static:`)}function D(){return`new:${Date.now().toString(36)}:${Math.random().toString(36).slice(2,8)}`}function ae(e,t){let n=e[0]||`claude`,r=t?.[n]?.models?.[0]||`default`;return{id:D(),name:`New Agent`,cli:n,model:r,role:``,status:`idle`,source:`db`}}function oe(e){return Array.isArray(e)?e.map(O).filter(Boolean):e.data.map(O).filter(Boolean)}function O(e){if(!e||typeof e!=`object`)return null;let t=e,n=typeof t.id==`string`?t.id:``,r=typeof t.name==`string`?t.name:``;if(!n||!r)return null;let i=t.source===`static`||n.startsWith(`static:`)?`static`:`db`,a={id:n,name:r,cli:typeof t.cli==`string`&&t.cli?t.cli:`claude`,model:typeof t.model==`string`&&t.model?t.model:`default`,role:typeof t.role==`string`?t.role:``,source:i};return typeof t.status==`string`&&(a.status=t.status),a}function k(e){return e.name.trim()?e.cli.trim()?e.model.trim()?null:`Model is required`:`CLI is required`:`Name is required`}function A(e){return e.some(e=>k(e)!==null)}function se(e,t){return JSON.stringify(e.map(F))===JSON.stringify(t.map(F))}function j(e,t){let n=new Map(e.map(e=>[e.id,e])),r=new Set(t.map(e=>e.id)),i=[],a=[],o=[];for(let e of t){let t=n.get(e.id);if(!t||e.id.startsWith(`new:`)){i.push(e);continue}let r=ce(t,e);Object.keys(r).length>0&&a.push({before:t,after:e,patch:r})}for(let t of e)!r.has(t.id)&&!E(t)&&o.push(t);return{added:i,updated:a,removed:o}}function M(e,t){let n=j(e,t);return{added:n.added.length,updated:n.updated.length,removed:n.removed.length}}async function N(e,t,n){let r=j(t,n);for(let t of r.removed)await e.delete(`/api/employees/${encodeURIComponent(t.id)}`);for(let t of r.added)await e.post(`/api/employees`,P(t));for(let t of r.updated)await e.put(`/api/employees/${encodeURIComponent(t.after.id)}`,t.patch)}function ce(e,t){if(E(t))return e.model===t.model?{}:{model:t.model};let n={};for(let r of[`name`,`cli`,`model`,`role`,`status`])(e[r]||``)!==(t[r]||``)&&(n[r]=t[r]||``);return n}function P(e){return{name:e.name.trim()||`New Agent`,cli:e.cli.trim()||`claude`,model:e.model.trim()||`default`,role:e.role.trim()}}function F(e){let t={id:e.id,name:e.name,cli:e.cli,model:e.model,role:e.role,source:e.source};return e.status!==void 0&&(t.status=e.status),t}function I({employee:e,index:t,cliOptions:n,cliMeta:r,onChange:i,onRemove:a}){let o=E(e),s=y(g(e.cli,r).models,e.model),c=k(e),l=Array.from(new Set([...n,e.cli,`claude`])).filter(Boolean);return(0,w.jsxs)(`fieldset`,{className:`settings-runtime-employee-row`,children:[(0,w.jsxs)(`legend`,{children:[e.name||`Employee ${t+1}`,o?(0,w.jsx)(`span`,{children:`static`}):null]}),(0,w.jsxs)(`div`,{className:`settings-runtime-employee-grid`,children:[(0,w.jsx)(d,{id:`runtime-employee-${e.id}-name`,label:`Name`,value:e.name,disabled:o,error:c===`Name is required`?c:null,onChange:e=>i({name:e})}),(0,w.jsx)(u,{id:`runtime-employee-${e.id}-cli`,label:`CLI`,value:e.cli,disabled:o,options:l.map(e=>({value:e,label:g(e,r).label||e})),onChange:e=>{i({cli:e,model:g(e,r).models[0]||`default`})}}),(0,w.jsx)(u,{id:`runtime-employee-${e.id}-model`,label:`Model`,value:e.model,options:s.length>0?s:[{value:`default`,label:`default`}],onChange:e=>i({model:e})}),(0,w.jsx)(d,{id:`runtime-employee-${e.id}-role`,label:`Role`,value:e.role,disabled:o,onChange:e=>i({role:e})})]}),(0,w.jsxs)(`div`,{className:`settings-runtime-employee-footer`,children:[(0,w.jsxs)(`span`,{children:[e.source,e.status?` / ${e.status}`:``]}),(0,w.jsx)(`button`,{type:`button`,className:`settings-action settings-action-discard`,disabled:o,onClick:a,"aria-label":`Remove employee ${e.name||t+1}`,children:`Remove`})]})]})}function le({roster:e,original:t,cliOptions:n,cliMeta:r,loading:i,error:a,onRosterChange:s}){let c=M(t,e),l=A(e);function u(t,n){s(e.map((e,r)=>r===t?{...e,...n}:e))}function d(t){s(e.filter((e,n)=>n!==t))}return(0,w.jsxs)(o,{title:`Employees`,hint:`Runtime dispatch roster. Static employees keep their locked identity; database employees can be edited.`,children:[(0,w.jsxs)(`div`,{className:`settings-runtime-employee-summary`,children:[(0,w.jsxs)(`span`,{children:[`+`,c.added]}),(0,w.jsxs)(`span`,{children:[`~`,c.updated]}),(0,w.jsxs)(`span`,{children:[`-`,c.removed]}),l?(0,w.jsx)(`strong`,{children:`Fix invalid rows before saving.`}):null]}),i?(0,w.jsx)(`p`,{className:`settings-agent-note`,children:`Loading employees...`}):null,a?(0,w.jsx)(`p`,{className:`settings-field-error`,role:`alert`,children:a}):null,e.length===0?(0,w.jsx)(`p`,{className:`settings-empty`,children:`No runtime employees configured.`}):(0,w.jsx)(`div`,{className:`settings-runtime-employee-list`,children:e.map((e,t)=>(0,w.jsx)(I,{employee:e,index:t,cliOptions:n,cliMeta:r??null,onChange:e=>u(t,e),onRemove:()=>d(t)},e.id))}),(0,w.jsx)(`div`,{className:`settings-employee-footer-bar`,children:(0,w.jsx)(`button`,{type:`button`,className:`settings-action settings-action-discard`,onClick:()=>s([...e,ae(n,r)]),children:`+ Add employee`})})]})}var L=new Set([`runtimeEmployees`,`flushCli`,`flushModel`]);function R(e){let t={},n=null,r=null;for(let[i,a]of Object.entries(e))i===`runtimeEmployees`?r=a:i===`flushCli`||i===`flushModel`?(n??={},n[i===`flushCli`?`cli`:`model`]=String(a??``)):L.has(i)||(t[i]=a);return{settingsBundle:t,flushPatch:n,runtimeEmployeesNext:r}}async function ue(e){let{client:t,bundle:n,employeeDraft:r,employeeOriginal:i}=e;if(A(r))throw Error(`runtime employees: fix invalid rows before saving`);let{settingsBundle:a,flushPatch:o,runtimeEmployeesNext:s}=R(n),c=null;try{if(Object.keys(a).length>0){let e=await t.put(`/api/settings`,S(a));c=e&&typeof e==`object`&&`data`in e?e.data:e}}catch(e){throw Error(`settings save failed: ${e instanceof Error?e.message:String(e)}`)}try{o&&await t.put(`/api/memory-files/settings`,o)}catch(e){throw Error(`flush settings save failed: ${e instanceof Error?e.message:String(e)}`)}try{s&&await N(t,i,s)}catch(e){throw Error(`runtime employees save failed: ${e instanceof Error?e.message:String(e)}`)}return c}function z(e){if(!(e instanceof r)||e.status!==409)return null;try{return JSON.parse(e.detail).settings??null}catch{return null}}function B({port:e,client:t,dirty:n,registerSave:r}){let{state:o,refresh:u,setData:d}=a(t,`/api/settings`),[p,m]=(0,C.useState)({cli:``,provider:``,model:``,effort:``,workingDir:``,permissions:`auto`}),[x,te]=(0,C.useState)({}),[S,T]=(0,C.useState)({}),[E,D]=(0,C.useState)(!0),[ae,O]=(0,C.useState)(null),[k,j]=(0,C.useState)([]),[M,N]=(0,C.useState)([]),[ce,P]=(0,C.useState)(!0),[F,I]=(0,C.useState)(null),[L,R]=(0,C.useState)(null),[B,de]=(0,C.useState)({}),[fe,pe]=(0,C.useState)(!1),[me,he]=(0,C.useState)(null),[ge,_e]=(0,C.useState)(!1),[ve,ye]=(0,C.useState)(null),V=(0,C.useCallback)(async()=>{try{let e=await t.get(`/api/cli-registry`);R(ee(e&&typeof e==`object`&&`data`in e?e.data:e))}catch{R(null)}},[t]),be=(0,C.useCallback)(async()=>{try{de(await t.get(`/api/cli-status`))}catch{de({})}},[t]),H=(0,C.useCallback)(async()=>{D(!0),O(null);try{let e=await t.get(`/api/memory-files`),n={cli:e.cli||``,model:e.model||``};te(n),T(n)}catch(e){O(e instanceof Error?e.message:String(e))}finally{D(!1)}},[t]),U=(0,C.useCallback)(async()=>{P(!0),I(null);try{let e=oe(await t.get(`/api/employees`));j(e),N(e)}catch(e){I(e instanceof Error?e.message:String(e))}finally{P(!1)}},[t]);(0,C.useEffect)(()=>{V(),be(),H(),U()},[V,be,U,H]),(0,C.useEffect)(()=>{if(o.kind!==`ready`)return;let e=Object.keys(o.data.perCli||{}),t=o.data.cli||e[0]||``,n=f(o.data.permissions),r=g(t,L);m({cli:t,provider:o.data.perCli?.[t]?.provider||r.defaultProvider||``,model:_(t,o.data.perCli,o.data.activeOverrides),effort:h(t,o.data.perCli,o.data.activeOverrides),workingDir:o.data.workingDir||``,permissions:n.mode===`custom`?n.tokens:`auto`})},[L,o]),(0,C.useEffect)(()=>()=>{for(let e of Array.from(n.pending.keys()))(e===`cli`||e===`workingDir`||e===`permissions`||e===`runtimeEmployees`||e===`flushCli`||e===`flushModel`||e.startsWith(`activeOverrides.`))&&n.remove(e)},[n]);let W=(0,C.useCallback)((e,t)=>n.set(e,t),[n]),G=(0,C.useCallback)(async()=>{let e=n.saveBundle();if(Object.keys(e).length===0)return;let r=await ue({client:t,bundle:e,employeeDraft:M,employeeOriginal:k});n.clear(),r&&d(r),await u(),await V(),await H(),await U()},[t,n,M,k,V,U,H,u,d]);(0,C.useEffect)(()=>{if(r)return r(G),()=>r(null)},[r,G]);let K=o.kind===`ready`?o.data:{},q=K.perCli||{},J=K.activeOverrides||{},Y=(0,C.useMemo)(()=>Object.keys(q),[q]),X=g(p.cli,L),Z=p.provider||X.defaultProvider||X.providers?.[0]||``,xe=p.cli===`pi`,Q=!xe&&(X.providers?.length??0)>0,Se=y(Q&&X.modelsByProvider?.[Z]||X.models,p.model),Ce=Q?b(X,p.model,X.effortsByProvider?.[Z]||X.efforts,Z):b(X,p.model),we=p.workingDir.trim()?null:`Required`,Te=(0,C.useCallback)(async e=>{pe(!0),he(null);try{let n=await t.post(`/api/settings/runtime-default-migration`,{action:e}),r;r=`settings`in n&&n.settings?n.settings:`data`in n&&n.data?n.data:n,d(r)}catch(e){let t=z(e);if(t){d(t);return}he(i(e))}finally{pe(!1)}},[t,d]),Ee=(0,C.useCallback)(async e=>{_e(!0),ye(null);try{let n=await t.post(`/api/settings/multi-session-default-migration`,{action:e}),r;r=`settings`in n&&n.settings?n.settings:`data`in n&&n.data?n.data:n,d(r)}catch(e){let t=z(e);if(t){d(t);return}ye(i(e))}finally{_e(!1)}},[t,d]);if(o.kind===`loading`)return(0,w.jsx)(s,{});if(o.kind===`offline`)return(0,w.jsx)(c,{port:e});if(o.kind===`error`)return(0,w.jsx)(l,{message:o.message});function De(){for(let e of Array.from(n.pending.keys()))e.startsWith(`activeOverrides.`)&&n.remove(e)}function $(e){m(e)}return(0,w.jsxs)(`form`,{className:`settings-page-form`,onSubmit:e=>{e.preventDefault(),G()},children:[K.runtimeDefaultMigration?.state===`pending`?(0,w.jsxs)(`div`,{className:`settings-inline-notice`,role:`status`,children:[(0,w.jsx)(`strong`,{children:`기본 런타임 변경 안내`}),(0,w.jsx)(`p`,{children:`기존 선택은 유지됩니다. Codex App으로 전환하거나 현재 런타임을 계속 사용할 수 있습니다.`}),(0,w.jsxs)(`div`,{className:`settings-inline-actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`settings-action settings-action-save`,disabled:fe,onClick:()=>void Te(`accept`),children:`Codex App 사용`}),(0,w.jsx)(`button`,{type:`button`,className:`settings-action settings-action-secondary`,disabled:fe,onClick:()=>void Te(`keep`),children:`현재 런타임 유지`})]}),me?(0,w.jsx)(`span`,{className:`settings-field-error`,role:`alert`,children:me}):null]}):null,K.multiSessionDefaultMigration?.state===`pending`?(0,w.jsxs)(`div`,{className:`settings-inline-notice`,role:`status`,children:[(0,w.jsx)(`strong`,{children:`다중 세션 안내`}),(0,w.jsx)(`p`,{children:`대화 세션을 여러 개 열 수 있습니다. 켜면 동시 실행도 2로 올라가서, 두 번째 세션이 첫 번째가 끝나기를 기다리지 않습니다. 지금 설정은 그대로 유지됩니다.`}),(0,w.jsxs)(`div`,{className:`settings-inline-actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`settings-action settings-action-save`,disabled:ge,onClick:()=>void Ee(`accept`),children:`다중 세션 켜기`}),(0,w.jsx)(`button`,{type:`button`,className:`settings-action settings-action-secondary`,disabled:ge,onClick:()=>void Ee(`keep`),children:`지금 설정 유지`})]}),ve?(0,w.jsx)(`span`,{className:`settings-field-error`,role:`alert`,children:ve}):null]}):null,B[p.cli]?.probeState===`checking`?(0,w.jsx)(`div`,{className:`settings-inline-notice`,role:`status`,"aria-live":`polite`,children:`상태 확인 중`}):null,B[p.cli]?.probeState===`failing`?(0,w.jsxs)(`div`,{className:`settings-inline-notice`,role:`alert`,children:[`상태 확인 실패 (재시도 중)`,B[p.cli]?.probeError?`: ${B[p.cli].probeError}`:``]}):null,(0,w.jsx)(ne,{cli:p.cli,cliOptions:Y.length>0?Y:[p.cli||`claude`],provider:Z,providerOptions:xe?[]:X.providers||[],model:p.model,modelOptions:Se,effort:p.effort,effortOptions:Ce,workingDir:p.workingDir,workingDirError:we,cliMeta:L,onCliChange:e=>{let t=g(e,L),n={...p,cli:e,provider:q[e]?.provider||t.defaultProvider||``,model:_(e,q,J),effort:h(e,q,J)};De(),$(n),W(`cli`,{value:e,original:K.cli||``,valid:!0})},onProviderChange:e=>{let t=X.modelsByProvider?.[e]||[],n=X.effortsByProvider?.[e]||[],r=t.includes(p.model)?p.model:t[0]||``,i=v(X,r,p.effort,n,e);$({...p,provider:e,model:r,effort:i}),W(`perCli.${p.cli}.provider`,{value:e,original:q[p.cli]?.provider||X.defaultProvider||``,valid:!0}),W(`activeOverrides.${p.cli}.model`,{value:r,original:_(p.cli,q,J),valid:r.trim().length>0}),W(`activeOverrides.${p.cli}.effort`,{value:i,original:h(p.cli,q,J),valid:!0})},onModelChange:e=>{let t=v(X,e,p.effort,Q?X.effortsByProvider?.[Z]||X.efforts:void 0,Q?Z:void 0);$({...p,model:e,effort:t}),W(`activeOverrides.${p.cli}.model`,{value:e,original:_(p.cli,q,J),valid:e.trim().length>0}),t!==p.effort&&W(`activeOverrides.${p.cli}.effort`,{value:t,original:h(p.cli,q,J),valid:!0})},onEffortChange:e=>{$({...p,effort:e}),W(`activeOverrides.${p.cli}.effort`,{value:e,original:h(p.cli,q,J),valid:!0})},onWorkingDirChange:e=>{$({...p,workingDir:e}),W(`workingDir`,{value:e,original:K.workingDir||``,valid:e.trim().length>0})}}),(0,w.jsx)(re,{value:p.permissions,onChange:e=>{$({...p,permissions:e}),W(`permissions`,{value:e,original:K.permissions??`auto`,valid:e===`auto`||e.length>0})}}),(0,w.jsx)(ie,{activeCli:p.cli,flushCli:S.cli||``,flushModel:S.model||``,cliOptions:Y,cliMeta:L,modelOptions:y(g(S.cli||p.cli,L).models,S.model||``),loading:E,error:ae,onFlushCliChange:e=>{let t=e&&g(e,L).models[0]||``;T({cli:e,model:t}),W(`flushCli`,{value:e,original:x.cli||``,valid:!0}),W(`flushModel`,{value:t,original:x.model||``,valid:!0})},onFlushModelChange:e=>{T({...S,model:e}),W(`flushModel`,{value:e,original:x.model||``,valid:!0})}}),(0,w.jsx)(le,{roster:M,original:k,cliOptions:Y,cliMeta:L,loading:ce,error:F,onRosterChange:e=>{if(N(e),se(e,k)){n.remove(`runtimeEmployees`);return}n.set(`runtimeEmployees`,{value:e,original:k,valid:!A(e)})}})]})}export{z as conflictSettingsFromError,B as default,R as splitAgentSaveBundle};