wip: Campaign LA LA2 probe mode + idle policy — INCOMPLETE, stopped mid-task
Agent was stopped for token budget. Landed here: probe flag through LiveSessionConnectOptions + the StartCore short-circuit, the mode field with JsonRequired-to-semantic-validation move, host exit-code mapping, and 34 passing tests including 3 new probe tests (agent last reported green before the stop). NOT DONE: the idle-policy unit tests (next step), full-suite verification, and the WSL run. Build/test state UNVERIFIED at this commit. Next session: finish idle policy tests, run Runtime+Headless Release suites Windows and WSL, then dispatch the Opus dual-lens review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
498f1c1182
commit
c601942467
14 changed files with 830 additions and 35 deletions
|
|
@ -319,7 +319,7 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
// doc). Gating on role keeps two sessions
|
||||
// writing the SAME field from ever racing —
|
||||
// only one role ever writes it.
|
||||
if (descriptor.Policy.Role
|
||||
if (descriptor.Policy?.Role
|
||||
== HeadlessBotPolicyRole.Recruit
|
||||
&& gateCoordinator is not null)
|
||||
{
|
||||
|
|
@ -366,13 +366,24 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
|
||||
hostLease = runtime.AcquireHostLease(
|
||||
$"headless:{descriptor.Id}");
|
||||
// Campaign LA slice LA2: a probe session's descriptor carries no
|
||||
// `policy` at all (the loader rejects the opposite pairing) — a
|
||||
// probe never reaches TrySelectCharacter/EnterWorld, so there is
|
||||
// no policy id to switch on. ProbeHeadlessBotPolicy reports
|
||||
// IsComplete unconditionally so HeadlessProcessScheduler treats
|
||||
// this session as already finished the instant it is
|
||||
// constructed, letting the scheduler's Run() loop return
|
||||
// immediately for a probe-only process instead of waiting for
|
||||
// SIGINT.
|
||||
policy = policyOverride
|
||||
?? HeadlessBotPolicyFactory.Create(
|
||||
descriptor.Policy,
|
||||
runtime,
|
||||
() => _pendingConfirmation,
|
||||
RespondToConfirmation,
|
||||
gateCoordinator);
|
||||
?? (descriptor.Mode == HeadlessSessionMode.Probe
|
||||
? new ProbeHeadlessBotPolicy()
|
||||
: HeadlessBotPolicyFactory.Create(
|
||||
descriptor.Policy!,
|
||||
runtime,
|
||||
() => _pendingConfirmation,
|
||||
RespondToConfirmation,
|
||||
gateCoordinator));
|
||||
policySubscription = runtime.Subscribe(policy);
|
||||
diagnostics.Lifecycle(
|
||||
descriptor.Id,
|
||||
|
|
@ -636,11 +647,18 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
_stoppedGeneration);
|
||||
// Campaign LA slice LA1: "exited" = terminal — the sole
|
||||
// point every disposal path (graceful and post-
|
||||
// quarantine) converges on.
|
||||
// quarantine) converges on. LA2: a probe session that
|
||||
// never faulted reports reason "probe" here instead of
|
||||
// "disposed" — the pinned contract's exit event for a
|
||||
// successful probe.
|
||||
_statusWriter.Exited(
|
||||
_descriptor.Id,
|
||||
_faulted ? 1 : 0,
|
||||
_faulted ? "fault" : "disposed");
|
||||
_faulted
|
||||
? "fault"
|
||||
: _descriptor.Mode == HeadlessSessionMode.Probe
|
||||
? "probe"
|
||||
: "disposed");
|
||||
_disposeStage++;
|
||||
_disposed = true;
|
||||
break;
|
||||
|
|
@ -717,7 +735,8 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
_descriptor.Endpoint.Port,
|
||||
_descriptor.Account,
|
||||
password,
|
||||
MapCharacterSelector(_descriptor.Character));
|
||||
MapCharacterSelector(_descriptor.Character),
|
||||
Probe: _descriptor.Mode == HeadlessSessionMode.Probe);
|
||||
LiveSessionStartResult result = _liveSession.Start(options);
|
||||
if (result.Selection is { } selection)
|
||||
_accountName = selection.AccountName;
|
||||
|
|
@ -964,12 +983,19 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
return declared;
|
||||
}
|
||||
|
||||
private static LiveSessionCharacterSelector MapCharacterSelector(
|
||||
HeadlessCharacterSelector selector) =>
|
||||
new(
|
||||
selector.Index,
|
||||
selector.Id,
|
||||
selector.Name);
|
||||
/// <summary>Campaign LA slice LA2: <see langword="null"/> for a probe
|
||||
/// session (the loader guarantees <c>Character</c> is omitted whenever
|
||||
/// <c>Mode</c> is <see cref="HeadlessSessionMode.Probe"/>) — a probe
|
||||
/// never reaches <c>TrySelectCharacter</c>, so "no selector configured"
|
||||
/// is the correct, harmless mapping.</summary>
|
||||
private static LiveSessionCharacterSelector? MapCharacterSelector(
|
||||
HeadlessCharacterSelector? selector) =>
|
||||
selector is null
|
||||
? null
|
||||
: new(
|
||||
selector.Index,
|
||||
selector.Id,
|
||||
selector.Name);
|
||||
|
||||
private RuntimeSessionStartResult Convert(
|
||||
LiveSessionStartResult result)
|
||||
|
|
@ -988,6 +1014,8 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
RuntimeSessionStartStatus.Deferred,
|
||||
LiveSessionStartStatus.Failed =>
|
||||
RuntimeSessionStartStatus.Failed,
|
||||
LiveSessionStartStatus.ProbeComplete =>
|
||||
RuntimeSessionStartStatus.ProbeComplete,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(result),
|
||||
result.Status,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue