sync: update from private repo (0cddeaa)

This commit is contained in:
oss-sync
2026-06-04 00:34:55 +00:00
parent f5c7666f6b
commit 21be01b699
19 changed files with 1065 additions and 37 deletions
+44 -3
View File
@@ -37,6 +37,13 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
const [sendError, setSendError] = useState<string | null>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
// Snapshot of comments.length at submit start. We hold "submitting" until
// (a) the new user comment is reflected in the list AND (b) the job is
// visibly busy (= picked up by a worker). Without this, the gap between the
// POST resolving and the worker dispatching the job lets the user fire
// multiple sends in a row.
const submitBaselineRef = useRef<number | null>(null);
const submitTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [isAtBottom, setIsAtBottom] = useState(true);
const [newMessageCount, setNewMessageCount] = useState(0);
const prevCommentCountRef = useRef(comments.length);
@@ -90,18 +97,32 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
setAttachments(prev => prev.filter(a => a.name !== name));
};
const releaseSubmitting = () => {
if (submitTimeoutRef.current) {
clearTimeout(submitTimeoutRef.current);
submitTimeoutRef.current = null;
}
submitBaselineRef.current = null;
setSubmitting(false);
};
const handleSubmit = async () => {
if ((!draft.trim() && attachments.length === 0) || submitting) return;
setSendError(null);
setSubmitting(true);
submitBaselineRef.current = comments.length;
try {
await onSubmit(draft, attachments.length > 0 ? attachments : undefined);
setDraft('');
setAttachments([]);
// Hold the lock until the agent is visibly responding (see effect below).
// Safety net: if the worker never picks the job up (queue stuck, server
// crash, etc.), release the lock after 10s so the user isn't trapped.
if (submitTimeoutRef.current) clearTimeout(submitTimeoutRef.current);
submitTimeoutRef.current = setTimeout(releaseSubmitting, 10000);
} catch (e) {
setSendError(e instanceof Error && e.message ? e.message : '送信に失敗しました');
} finally {
setSubmitting(false);
releaseSubmitting();
}
};
@@ -165,6 +186,26 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
const canInterject = jobStatus === 'running' || jobStatus === 'waiting_subtasks';
const inputLocked = jobStatus === 'dispatching';
// Release the submit lock once the agent is visibly responding: the new user
// comment is reflected in the list AND the job has been picked up by a worker
// (isBusy=true). This bridges the queued->dispatching gap where a stale
// re-enabled send button would otherwise allow a double submit.
useEffect(() => {
if (!submitting) return;
const baseline = submitBaselineRef.current;
if (baseline === null) return;
if (comments.length > baseline && isBusy) {
releaseSubmitting();
}
}, [submitting, comments.length, isBusy]);
// Clear the safety-net timeout on unmount.
useEffect(() => {
return () => {
if (submitTimeoutRef.current) clearTimeout(submitTimeoutRef.current);
};
}, []);
// During an active run, suppress the trailing thinking comment so the
// live SSE preview is the single source of truth for in-flight text.
// We keep the comment in history (MovementGroup will render it once the
@@ -387,7 +428,7 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
/>
<button
onClick={() => fileInputRef.current?.click()}
disabled={inputLocked}
disabled={inputLocked || submitting}
className="flex-shrink-0 w-9 h-9 flex items-center justify-center text-slate-500 hover:text-slate-900 hover:bg-surface rounded-md transition-colors disabled:opacity-50 disabled:hover:bg-transparent disabled:cursor-not-allowed"
title="ファイルを添付"
aria-label="ファイルを添付"
@@ -24,6 +24,13 @@ export function SafetyForm({ config, onChange }: SectionFormProps) {
<HelpText> movement デフォルト: 3</HelpText>
</div>
<div>
<FieldLabel>Max Tool Loop Repeats</FieldLabel>
<FieldInput type="number" value={safety.maxToolLoopRepeats ?? 5}
onChange={v => onChange('safety.maxToolLoopRepeats', Number(v))} />
<HelpText> movement 2デフォルト: 51</HelpText>
</div>
<div>
<FieldLabel>Prompt Guard Ratio</FieldLabel>
<FieldInput type="number" value={safety.promptGuardRatio ?? 0.8}