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.
- package/README.md +94 -3
- package/dist/server.js +11 -0
- package/dist/server.js.map +1 -1
- package/dist/src/agent/spawn/process-kill.js +96 -2
- package/dist/src/agent/spawn/process-kill.js.map +1 -1
- package/dist/src/agent/spawn.js +34 -65
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/bgtask/runner.js +26 -21
- package/dist/src/bgtask/runner.js.map +1 -1
- package/dist/src/cli/acp-client.js +11 -5
- package/dist/src/cli/acp-client.js.map +1 -1
- package/dist/src/cli/employee-handler.js +3 -2
- package/dist/src/cli/employee-handler.js.map +1 -1
- package/dist/src/code-mode/acp-host.js +22 -10
- package/dist/src/code-mode/acp-host.js.map +1 -1
- package/dist/src/core/config.js +14 -0
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +16 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/employees.js +22 -4
- package/dist/src/core/employees.js.map +1 -1
- package/dist/src/core/windows-shell.js +63 -0
- package/dist/src/core/windows-shell.js.map +1 -0
- package/dist/src/discord/bot.js +199 -147
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/gateway-supervisor.js +303 -0
- package/dist/src/discord/gateway-supervisor.js.map +1 -0
- package/dist/src/discord/rest-scheduler.js +448 -0
- package/dist/src/discord/rest-scheduler.js.map +1 -0
- package/dist/src/discord/send-only-client.js +59 -43
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/manager/lifecycle.js +7 -2
- package/dist/src/manager/lifecycle.js.map +1 -1
- package/dist/src/manager/notes/capabilities.js +9 -7
- package/dist/src/manager/notes/capabilities.js.map +1 -1
- package/dist/src/memory/bootstrap.js +5 -1
- package/dist/src/memory/bootstrap.js.map +1 -1
- package/dist/src/memory/host-toolchain.js +156 -0
- package/dist/src/memory/host-toolchain.js.map +1 -0
- package/dist/src/messaging/channel-capabilities.js +30 -0
- package/dist/src/messaging/channel-capabilities.js.map +1 -0
- package/dist/src/messaging/delivery-outcome.js +73 -0
- package/dist/src/messaging/delivery-outcome.js.map +1 -0
- package/dist/src/messaging/draft-stream.js +154 -0
- package/dist/src/messaging/draft-stream.js.map +1 -0
- package/dist/src/messaging/runtime.js +3 -9
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +40 -8
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/types.js +22 -1
- package/dist/src/messaging/types.js.map +1 -1
- package/dist/src/notes/search.js +6 -4
- package/dist/src/notes/search.js.map +1 -1
- package/dist/src/prompt/builder.js +110 -6
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/conversation-context.js +23 -0
- package/dist/src/prompt/conversation-context.js.map +1 -0
- package/dist/src/prompt/templates/a1-system.md +28 -12
- package/dist/src/prompt/templates/control-system.md +19 -6
- package/dist/src/prompt/templates/employee.md +16 -3
- package/dist/src/security/path-guards.js +124 -27
- package/dist/src/security/path-guards.js.map +1 -1
- package/dist/src/slack/api.js +38 -5
- package/dist/src/slack/api.js.map +1 -1
- package/dist/src/slack/bot.js +214 -35
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/context.js +192 -0
- package/dist/src/slack/context.js.map +1 -0
- package/dist/src/slack/conversation.js +275 -0
- package/dist/src/slack/conversation.js.map +1 -0
- package/dist/src/slack/enrichment-cache.js +334 -0
- package/dist/src/slack/enrichment-cache.js.map +1 -0
- package/dist/src/slack/history.js +47 -8
- package/dist/src/slack/history.js.map +1 -1
- package/dist/src/slack/identity.js +99 -190
- package/dist/src/slack/identity.js.map +1 -1
- package/dist/src/slack/ingress.js +78 -2
- package/dist/src/slack/ingress.js.map +1 -1
- package/dist/src/slack/progress.js +39 -51
- package/dist/src/slack/progress.js.map +1 -1
- package/dist/src/slack/thread-tracker.js +78 -0
- package/dist/src/slack/thread-tracker.js.map +1 -1
- package/dist/src/telegram/bot.js +105 -56
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/telegram/update-offset.js +152 -0
- package/dist/src/telegram/update-offset.js.map +1 -0
- package/package.json +1 -1
- package/public/dist/assets/Agent-C5EvX-_v.js +1 -0
- package/public/dist/assets/{manager-Bjuh1jpq.js → manager-CP-Au860.js} +1 -1
- package/public/dist/manager/index.html +1 -1
- package/public/manager/src/settings/cli-status-polling.ts +84 -0
- package/public/manager/src/settings/pages/Agent.tsx +84 -5
- package/scripts/install-officecli.ps1 +38 -2
- package/scripts/install.ps1 +1 -1
- 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-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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 =
|
|
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
|
|
package/scripts/install.ps1
CHANGED
|
@@ -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};
|