dsh-harbor-evolution 0.8.0 → 0.8.2

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 CHANGED
@@ -14,11 +14,13 @@ npx --yes dsh-harbor-evolution@latest setup --project-root "$PWD"
14
14
 
15
15
  The setup command installs both required runtimes:
16
16
 
17
- - `harbor-dsh-evolution==0.8.0` in a managed Python environment.
18
- - `dsh-harbor-evolution@0.8.0` in the selected DSH profile.
17
+ - `harbor-dsh-evolution==0.8.2` in a managed Python environment.
18
+ - `dsh-harbor-evolution@0.8.2` in the selected DSH profile.
19
19
 
20
20
  It then stores the absolute Harbor executable paths and a fallback `projectRoot` in the profile's `harbor-evolution` block and verifies the integration. Agent Tool calls always use the calling session's absolute working directory as their project root; the configured value remains the Web Workbench and non-Agent fallback. Existing unrelated profile entries are preserved, and rerunning setup updates the same block.
21
21
 
22
+ Successful setup requires `harbor plugins list` to discover both `dsh-evolution` for Candidate Jobs and `dsh-historical-evaluation` for observe-existing Session Jobs.
23
+
22
24
  The default profile is `web`. Use `--profile headless` only when that is the profile you actually run. See all options with:
23
25
 
24
26
  ```bash
@@ -56,15 +58,24 @@ The Plugin registers:
56
58
  In the `web` profile, the same package also registers:
57
59
 
58
60
  - a localized nine-stage Workbench that directly exposes fixed experiment identities, Agent-visible Dataset queries/instructions, safe business-artifact previews, Ground Truth meta-evaluation, paginated per-Trial evidence and recommendations, Population validity/coverage, controlled optimization hypotheses, and Baseline/Gate deltas; raw JSON remains in the audit drawer;
61
+ - a first-class `Evaluate recent Sessions` action that previews up to ten safe Session records, shows the frozen Evaluator/Judge and diagnostic boundaries, requires explicit confirmation, runs in the background, and opens the completed Job;
59
62
  - descriptor-authorized Evaluator/Rubric source editing for `script` and `llm-as-judge` implementations, with optimistic concurrency and mandatory new identities;
60
63
  - a `harbor-dsh-evaluator/v1` interface shared by deterministic scripts and LLM-as-Judge implementations;
61
64
  - compact result cards for all Harbor Tool calls;
62
65
  - a `Harbor Evolution` Settings section that checks the configured project, Evaluation Stack, Jobs directory, and CLI paths, supports process-local `projectRoot` reload, and checks npm for a newer formal release without silently installing it.
63
66
 
64
- The Web UI is intentionally read-only. Starting an evaluation or deciding promotion remains an explicit Agent + Skill workflow, so a page refresh can never launch an expensive Job.
67
+ The Web UI is read-only except for two narrow, explicit workflows: descriptor-authorized Evaluator source updates and the confirmed Historical Session launcher. The launcher follows `Preview confirm background run open Job`; its private selection token never enters browser state. Page refreshes never start Jobs, and Candidate evaluation, comparison, Gate, promotion, deployment, and publishing remain explicit Agent + Skill workflows.
65
68
 
66
69
  A direct evaluation requires `candidatePath`, `datasetPath`, `stackPath`, and explicit `mode`; `promotion-eligible` additionally requires `policyPath`. Prefer the Skill because it will not run or compare Jobs until the material identities and evaluation contract are resolved.
67
70
 
71
+ ## Historical Session cold start
72
+
73
+ When the user does not provide a Dataset, the simplest entry is the `Evaluate recent Sessions` button in the Harbor tab. It previews up to ten recent completed business Sessions, shows only safe metadata plus the Evaluator, Judge, same-model coupling, estimated requests, expiry and local retention boundaries, and starts nothing until the user confirms. The Host keeps the short-lived selection token in memory; the browser receives only an opaque Preview id. The bundled Skill remains the conversational entry and uses the same Preview/Run services from the Agent's exact working directory.
74
+
75
+ After explicit confirmation, `harbor_session_diagnostic_run` receives only the `selectionToken` and an optional Job name. It revalidates the frozen Session and Feedback digests, materializes an immutable Historical Batch plus matching Dataset and Stack, and evaluates one Session Observation per Harbor Trial. The Job does not rerun a Candidate, cannot enter Promotion Gate, and records Evaluator Meta-Evaluation as `not-run` because evaluator reliability requires a separate independent Ground Truth workflow.
76
+
77
+ A Historical Trial may finish as `completed-unscored` when required evidence is insufficient. That is a normal Evaluator abstention, not a zero score or infrastructure failure; use Trial and Criterion coverage to interpret the result.
78
+
68
79
  ## Candidate model binding
69
80
 
70
81
  Before each Job, the Plugin snapshots the current DSH Agent selection—provider, model, and reasoning effort—then starts a per-Job local Model Broker. The Candidate uses the temporary `dsh-host` adapter through `dsh-host-broker` / `dsh-host-model-gateway/v1`; it receives only a short-lived Job capability file, never GPT Auth, Codex OAuth, or an upstream API key.
package/index.js CHANGED
@@ -8,6 +8,7 @@ import { loadBundledSkill } from './lib/official-skill.js'
8
8
  import { CandidateModelRuntime } from './lib/model-runtime.js'
9
9
  import { RUNTIME_POLICY } from './lib/runtime-identity.js'
10
10
  import { SessionDiagnosticService } from './lib/session-diagnostic.js'
11
+ import { HistoricalWebController } from './lib/historical-web.js'
11
12
  import { EvolutionService } from './lib/service.js'
12
13
  import { runHistoricalEvaluation } from './lib/evolution.js'
13
14
  import { installDashboardWeb } from './lib/web.js'
@@ -95,13 +96,14 @@ export function apply(ctx, config) {
95
96
  modelRuntime,
96
97
  runHistoricalEvaluation,
97
98
  })
99
+ const historicalWeb = new HistoricalWebController({ service, sessionDiagnostic })
98
100
  const serviceForTool = exec => {
99
101
  const projectRoot = synchronizeWorkbenchProjectRoot(service, exec)
100
102
  return new EvolutionService({ ...resolved, projectRoot }, metadata, modelRuntime)
101
103
  }
102
104
 
103
105
  ctx.skills.register(loadBundledSkill())
104
- installDashboardWeb(ctx, service)
106
+ installDashboardWeb(ctx, service, historicalWeb)
105
107
 
106
108
  ctx.tools.register(jsonTool({
107
109
  name: 'harbor_candidate_snapshot',
package/lib/client.js CHANGED
@@ -63,7 +63,44 @@ var dictionaries = {
63
63
  jobsHint: "\u70B9\u51FB Job \u540E\uFF0C\u6700\u591A\u518D\u70B9\u4E00\u6B21\u5373\u53EF\u8FDB\u5165\u5BF9\u5E94 Trial \u7684\u8BC1\u636E\u3002",
64
64
  workspace: "\u5DE5\u4F5C\u7A7A\u95F4",
65
65
  workspaceSelect: "\u9009\u62E9 Harbor \u5DE5\u4F5C\u7A7A\u95F4",
66
- empty: "\u8FD8\u6CA1\u6709\u53EF\u8BFB\u53D6\u7684 Harbor Job\u3002\u8BF7\u7528\u5B98\u65B9 Skill \u5B8C\u6210\u9700\u6C42\u6F84\u6E05\u548C\u521D\u59CB\u5316\u3002",
66
+ empty: "\u8FD8\u6CA1\u6709 Harbor Job\u3002\u53EF\u4EE5\u5148\u8BC4\u6D4B\u8FD9\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\u6700\u8FD1\u5B8C\u6210\u7684\u771F\u5B9E\u4F1A\u8BDD\u3002",
67
+ historicalLaunch: "\u8BC4\u6D4B\u6700\u8FD1\u4F1A\u8BDD",
68
+ historicalLaunchShort: "\u5F00\u59CB\u8BC4\u6D4B",
69
+ historicalLaunchHint: "\u6700\u591A 10 \u6761 \xB7 \u5148\u9884\u89C8\u518D\u8FD0\u884C",
70
+ historicalLaunchBody: "\u7528\u5F53\u524D DSH Agent \u5DF2\u5B8C\u6210\u7684\u771F\u5B9E\u4EFB\u52A1\u505A\u8BCA\u65AD\uFF0C\u4E0D\u91CD\u65B0\u8FD0\u884C Candidate\u3002",
71
+ historicalPreparing: "\u6B63\u5728\u67E5\u627E\u53EF\u8BC4\u6D4B\u4F1A\u8BDD\u2026",
72
+ historicalPreparingShort: "\u8BFB\u53D6\u4E2D\u2026",
73
+ historicalPreviewTitle: "\u786E\u8BA4\u5386\u53F2\u4F1A\u8BDD\u8BC4\u6D4B",
74
+ historicalPreviewHint: "\u8FD9\u91CC\u53EA\u5C55\u793A\u5B89\u5168\u5143\u6570\u636E\u3002\u786E\u8BA4\u524D\u4E0D\u4F1A\u5199\u5165 Batch\uFF0C\u4E5F\u4E0D\u4F1A\u542F\u52A8 Harbor Job\u3002",
75
+ historicalConfirm: "\u786E\u8BA4\u5E76\u5F00\u59CB\u8BC4\u6D4B",
76
+ historicalStarting: "\u6B63\u5728\u542F\u52A8\u2026",
77
+ historicalRunning: "\u5386\u53F2\u4F1A\u8BDD\u8BC4\u6D4B\u8FD0\u884C\u4E2D",
78
+ historicalRunningHint: "\u53EF\u4EE5\u5173\u95ED\u6B64\u7A97\u53E3\u7EE7\u7EED\u5DE5\u4F5C\u3002Harbor \u4F1A\u5728\u540E\u53F0\u8FD0\u884C\uFF0C\u5B8C\u6210\u540E\u81EA\u52A8\u6253\u5F00 Job\u3002",
79
+ historicalActive: "\u67E5\u770B\u8FD0\u884C\u72B6\u6001",
80
+ historicalActiveShort: "\u67E5\u770B\u72B6\u6001",
81
+ historicalCompleted: "\u8BC4\u6D4B\u5B8C\u6210\uFF0C\u6B63\u5728\u6253\u5F00 Job\u2026",
82
+ recentSessions: "\u672C\u6B21\u4F1A\u8BDD\u6837\u672C",
83
+ selectedSessions: "\u9009\u4E2D\u4F1A\u8BDD",
84
+ requestEstimate: "\u9884\u8BA1 Judge \u8BF7\u6C42",
85
+ tokenExpiry: "\u9884\u89C8\u6709\u6548\u671F",
86
+ generatorRole: "\u751F\u6210\u5668",
87
+ generatorRoleValue: "\u4EA7\u751F\u8FD9\u4E9B\u4F1A\u8BDD\u7684 DSH Agent",
88
+ evaluatorIdentity: "\u8BC4\u6D4B\u5668\u8EAB\u4EFD",
89
+ judgeIdentity: "Judge \u8EAB\u4EFD",
90
+ coupling: "\u6A21\u578B\u8026\u5408",
91
+ evidenceRetention: "\u8BC1\u636E\u4FDD\u7559",
92
+ historicalBoundaries: "\u672C\u6B21\u8FD0\u884C\u8FB9\u754C",
93
+ historicalBoundaryDetail: "\u4E0D\u8FD0\u884C Candidate \xB7 \u4E0D\u505A\u8BC4\u6D4B\u5668\u5143\u8BC4\u6D4B \xB7 \u4E0D\u8FDB\u5165 Gate / \u664B\u7EA7",
94
+ feedbackCounts: "\u53CD\u9988",
95
+ turnCounts: "\u8F6E\u6B21",
96
+ toolCounts: "\u5DE5\u5177\u8C03\u7528",
97
+ previewAgain: "\u91CD\u65B0\u9884\u89C8",
98
+ recent30Days: "\u4EC5\u770B\u6700\u8FD1 30 \u5929",
99
+ noEligibleHint: "\u5F53\u524D\u5DE5\u4F5C\u7A7A\u95F4\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u5DF2\u5B8C\u6210\u9876\u5C42\u4F1A\u8BDD\u3002\u5148\u5728\u8FD9\u4E2A\u76EE\u5F55\u5B8C\u6210\u4E00\u4E2A\u6709\u7528\u6237\u8F93\u5165\u548C Agent \u8F93\u51FA\u7684\u771F\u5B9E\u4EFB\u52A1\uFF0C\u6216\u6539\u7528\u663E\u5F0F Dataset\u3002",
100
+ narrowScanHint: "\u8FD9\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\u7684\u4F1A\u8BDD\u592A\u591A\u3002\u53EF\u4EE5\u628A\u626B\u63CF\u8303\u56F4\u7F29\u5230\u6700\u8FD1 30 \u5929\u540E\u91CD\u8BD5\u3002",
101
+ changedSessionHint: "\u9884\u89C8\u540E\u4F1A\u8BDD\u3001\u53CD\u9988\u6216\u5DE5\u4F5C\u7A7A\u95F4\u53D1\u751F\u4E86\u53D8\u5316\u3002\u4E3A\u4E86\u907F\u514D\u8BC4\u9519\u8BC1\u636E\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u3002",
102
+ historicalGenericError: "\u6CA1\u6709\u542F\u52A8 Job\u3002\u8BF7\u68C0\u67E5\u63D0\u793A\u540E\u91CD\u65B0\u9884\u89C8\u3002",
103
+ cancel: "\u53D6\u6D88",
67
104
  completed: "\u5DF2\u5B8C\u6210",
68
105
  partial: "\u5B8C\u6210\u4F46\u6709\u5F02\u5E38",
69
106
  failed: "\u8BFB\u53D6\u5931\u8D25",
@@ -300,7 +337,44 @@ var dictionaries = {
300
337
  jobsHint: "Open a Job, then reach Trial evidence in at most one more interaction.",
301
338
  workspace: "Workspace",
302
339
  workspaceSelect: "Select Harbor workspace",
303
- empty: "No readable Harbor Jobs yet. Use the official Skill to clarify and initialize the project.",
340
+ empty: "No Harbor Jobs yet. Start by evaluating recent completed Sessions in this workspace.",
341
+ historicalLaunch: "Evaluate recent Sessions",
342
+ historicalLaunchShort: "Start evaluation",
343
+ historicalLaunchHint: "Up to 10 \xB7 preview before running",
344
+ historicalLaunchBody: "Diagnose real tasks already completed by the current DSH Agent without rerunning a Candidate.",
345
+ historicalPreparing: "Finding eligible Sessions\u2026",
346
+ historicalPreparingShort: "Loading\u2026",
347
+ historicalPreviewTitle: "Confirm Historical Session evaluation",
348
+ historicalPreviewHint: "Only safe metadata is shown. No Batch is written and no Harbor Job starts until you confirm.",
349
+ historicalConfirm: "Confirm and start evaluation",
350
+ historicalStarting: "Starting\u2026",
351
+ historicalRunning: "Historical Session evaluation is running",
352
+ historicalRunningHint: "You can close this window and keep working. Harbor runs in the background and opens the Job when it completes.",
353
+ historicalActive: "View run status",
354
+ historicalActiveShort: "View status",
355
+ historicalCompleted: "Evaluation complete. Opening the Job\u2026",
356
+ recentSessions: "Session sample",
357
+ selectedSessions: "Selected Sessions",
358
+ requestEstimate: "Estimated Judge requests",
359
+ tokenExpiry: "Preview expires",
360
+ generatorRole: "Generator",
361
+ generatorRoleValue: "The DSH Agent that produced these Sessions",
362
+ evaluatorIdentity: "Evaluator identity",
363
+ judgeIdentity: "Judge identity",
364
+ coupling: "Model coupling",
365
+ evidenceRetention: "Evidence retention",
366
+ historicalBoundaries: "Run boundaries",
367
+ historicalBoundaryDetail: "No Candidate run \xB7 no Evaluator meta-evaluation \xB7 no Gate or promotion",
368
+ feedbackCounts: "Feedback",
369
+ turnCounts: "Turns",
370
+ toolCounts: "Tool calls",
371
+ previewAgain: "Preview again",
372
+ recent30Days: "Only last 30 days",
373
+ noEligibleHint: "No eligible completed top-level Sessions were found in this workspace. Complete a real task here with direct user input and Agent output, or use an explicit Dataset.",
374
+ narrowScanHint: "This workspace has too many Sessions to scan safely. Narrow the scan to the last 30 days and try again.",
375
+ changedSessionHint: "A Session, its feedback, or the workspace changed after Preview. Preview again so Harbor cannot evaluate stale evidence.",
376
+ historicalGenericError: "No Job was started. Review the message and preview again.",
377
+ cancel: "Cancel",
304
378
  completed: "Completed",
305
379
  partial: "Completed with errors",
306
380
  failed: "Read failed",
@@ -539,8 +613,11 @@ var CSS = `
539
613
  .hse-identity-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.hse-evidence-table{width:100%;border-collapse:collapse;font-size:10px}.hse-evidence-table th,.hse-evidence-table td{padding:9px;border-bottom:1px solid var(--dsw-alias-border-l1,#dce4f0);text-align:left;vertical-align:top}.hse-evidence-table th{color:var(--dsw-alias-label-secondary,#748096);font-weight:500}.hse-evidence-table code{overflow-wrap:anywhere}.hse-chip-list{display:flex;gap:6px;flex-wrap:wrap}.hse-chip-list span{padding:6px 8px;border-radius:999px;background:#2875ff12;font-size:9px}.hse-hypotheses{display:grid;gap:10px}.hse-hypothesis{padding:14px;border:1px solid #2875ff3d;border-radius:12px;background:linear-gradient(145deg,#2875ff0c,#44d9ff05)}.hse-hypothesis h4{margin:0 0 10px;font-size:13px}.hse-hypothesis dl{display:grid;grid-template-columns:150px minmax(0,1fr);gap:8px 12px;margin:0;font-size:10px}.hse-hypothesis dt{color:var(--dsw-alias-label-secondary,#748096)}.hse-hypothesis dd{margin:0;overflow-wrap:anywhere}.hse-gate-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.hse-decision{padding:8px 12px;border-radius:999px;color:#126d50;background:#23ba8318;font-weight:800}.hse-decision[data-pass=false]{color:#b52f45;background:#ee647818}
540
614
  .hse-report-table button{border:0;color:var(--ocean-600);background:none;text-align:left;cursor:pointer;font:inherit}.hse-report-table tr[data-selected=true]{background:#2875ff10}.hse-report-score{font-size:15px;font-weight:800}.hse-report-score[data-valid=false]{color:var(--coral-500)}.hse-report-detail{margin-top:12px;border:1px solid #2875ff40;border-radius:13px;overflow:hidden}.hse-report-detail-head{display:flex;justify-content:space-between;gap:12px;padding:14px 16px;background:linear-gradient(145deg,#2875ff14,#44d9ff08)}.hse-report-detail-head h4{margin:0;font-size:14px}.hse-report-detail-head span,.hse-report-detail-head code{display:block;margin-top:4px;color:var(--dsw-alias-label-secondary,#748096);font-size:9px}.hse-report-detail-head b{font-size:25px}.hse-report-criteria{display:grid;gap:9px;padding:14px}.hse-report-criterion{padding:12px;border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f7fb)}.hse-report-criterion header{display:flex;justify-content:space-between;gap:10px}.hse-report-criterion header b:last-child{font-size:17px}.hse-report-criterion dl{display:grid;grid-template-columns:92px minmax(0,1fr);gap:8px 10px;margin:10px 0 0;font-size:10px;line-height:1.55}.hse-report-criterion dt{color:var(--dsw-alias-label-secondary,#748096)}.hse-report-criterion dd{margin:0;overflow-wrap:anywhere}.hse-report-recommendation{color:var(--ocean-600)}
541
615
  .hse-stage-nav{grid-template-columns:repeat(9,minmax(88px,1fr))}.hse-report-compare{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px;padding:14px;align-items:start}.hse-report-compare .hse-report-criteria{padding:0}.hse-meta-flow{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.hse-meta-flow div{position:relative;padding:13px;border-radius:10px;background:#2875ff0f;font-size:10px}.hse-meta-flow div:not(:last-child):after{content:'\u2192';position:absolute;right:-8px;top:50%;z-index:1;color:var(--ocean-600);font-weight:800}.hse-badcase{color:#b52f45;background:#ee647817!important}.hse-hook-state{margin-bottom:12px;padding:11px 13px;border-left:3px solid var(--ocean-600);border-radius:8px;background:#2875ff0d;font-size:10px}.hse-hook-state[data-executed=false]{border-color:var(--amber-500);background:#e4a23b12}
616
+ .hse-launch-card{display:flex;align-items:center;gap:13px;margin:0 0 18px;padding:15px 17px;border:1px solid #2875ff40;border-radius:16px;background:linear-gradient(135deg,#2875ff16,#44d9ff0b);box-shadow:0 10px 30px #0a4b8f0d}.hse-launch-mark{display:grid;place-items:center;flex:0 0 38px;height:38px;border-radius:12px;color:#fff;background:linear-gradient(145deg,var(--ocean-600),var(--ocean-300));box-shadow:0 8px 18px #2875ff35;font-size:18px}.hse-launch-copy{display:grid;gap:3px;min-width:0}.hse-launch-copy b{font-size:13px}.hse-launch-copy span{color:var(--dsw-alias-label-secondary,#68778d);font-size:10px;line-height:1.5}.hse-launch-copy small{color:var(--ocean-600);font-size:9px;font-weight:800}.hse-launch-button{margin-left:auto;padding:10px 15px;border:0;border-radius:10px;color:#fff;background:var(--whale-500);box-shadow:0 8px 20px #2875ff30;cursor:pointer;font:inherit;font-size:11px;font-weight:800;white-space:nowrap}.hse-launch-button:disabled{opacity:.55;cursor:wait}.hse-launch-overlay{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;padding:18px;background:#03152fa3;backdrop-filter:blur(5px)}.hse-launch-dialog{display:flex;flex-direction:column;width:min(780px,calc(100vw - 32px));max-height:min(860px,calc(100vh - 36px));overflow:hidden;border:1px solid var(--dsw-alias-border-l1,#d7e2ef);border-radius:20px;color:var(--dsw-alias-label-primary,#1d2a3d);background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 28px 90px #03152f6b}.hse-launch-head{display:flex;justify-content:space-between;gap:20px;padding:20px 22px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#e1e8f1)}.hse-launch-head span{color:var(--ocean-600);font-size:9px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.hse-launch-head h2{margin:5px 0 6px;font-size:20px}.hse-launch-head p{margin:0;color:var(--dsw-alias-label-secondary,#748096);font-size:10px;line-height:1.6}.hse-dialog-close{align-self:flex-start;width:30px;height:30px;border:1px solid var(--dsw-alias-border-l1,#d7e2ef);border-radius:9px;color:inherit;background:transparent;cursor:pointer;font-size:20px;line-height:1}.hse-launch-body{overflow:auto;padding:18px 22px}.hse-launch-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:13px}.hse-launch-summary div,.hse-launch-grid div{padding:11px;border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f7fb)}.hse-launch-summary span,.hse-launch-grid span{display:block;color:var(--dsw-alias-label-secondary,#748096);font-size:9px}.hse-launch-summary b,.hse-launch-grid b{display:block;margin-top:4px;overflow-wrap:anywhere;font-size:11px}.hse-launch-section{margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l1,#d7e2ef);border-radius:13px}.hse-launch-section h3{margin:0 0 10px;font-size:12px}.hse-session-list{display:grid;gap:7px;max-height:300px;overflow:auto}.hse-session-list article{padding:10px 11px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f7fb)}.hse-session-list article>div{display:flex;justify-content:space-between;gap:12px}.hse-session-list b{font-size:10px}.hse-session-list span,.hse-session-list p,.hse-session-list code{color:var(--dsw-alias-label-secondary,#748096);font-size:9px}.hse-session-list p{margin:6px 0 3px}.hse-session-list code{overflow-wrap:anywhere}.hse-launch-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.hse-boundary-note{margin:10px 0 0;padding:9px 11px;border-left:3px solid var(--amber-500);border-radius:7px;background:#e4a23b12;font-size:10px}.hse-run-state{display:grid;justify-items:center;gap:9px;padding:42px 18px;text-align:center}.hse-run-state b{font-size:16px}.hse-run-state span,.hse-run-state p{max-width:560px;margin:0;color:var(--dsw-alias-label-secondary,#748096);font-size:10px;line-height:1.6}.hse-launch-error{padding:15px;border-left:4px solid var(--coral-500);border-radius:10px;background:#ee647812}.hse-launch-error b{color:#bd3148;font-size:11px}.hse-launch-error p{margin:7px 0;font-size:11px;overflow-wrap:anywhere}.hse-launch-error span{color:var(--dsw-alias-label-secondary,#748096);font-size:10px;line-height:1.6}.hse-launch-actions{display:flex;justify-content:flex-end;gap:8px;padding:13px 22px;border-top:1px solid var(--dsw-alias-border-l1,#e1e8f1)}.hse-launch-actions button{padding:9px 13px;border:1px solid var(--dsw-alias-border-l1,#c8d6e7);border-radius:9px;color:inherit;background:transparent;cursor:pointer;font:inherit;font-size:10px}.hse-launch-actions .hse-confirm{border-color:var(--whale-500);color:#fff;background:var(--whale-500);font-weight:800}
617
+ .hse-launch-card{margin-top:14px}.hse-launch-button-short{display:none}
542
618
  @keyframes hse-spin{to{transform:rotate(360deg)}}@keyframes hse-pulse{50%{opacity:.38}}@keyframes hse-ripple{0%{transform:scale(.75);opacity:.4}70%,100%{transform:scale(1.12);opacity:0}}
543
- @media(max-width:900px){.hse-page{width:calc(100% - 20px)}.hse-meta-grid,.hse-kpis,.hse-identity-grid{grid-template-columns:repeat(2,1fr)}.hse-trial-layout,.hse-output-layout{grid-template-columns:1fr}.hse-trial-detail{position:static;max-height:none}.hse-components,.hse-governance-id{grid-template-columns:repeat(2,1fr)}.hse-drawer{width:100vw}.hse-workbench{padding:12px}.hse-stage-nav{top:62px}.hse-trial-tools{grid-template-columns:1fr 1fr}.hse-grid,.hse-checks,.hse-version-grid{grid-template-columns:1fr}.hse-hypothesis dl{grid-template-columns:1fr}}
619
+ @media(max-width:900px){.hse-page{width:calc(100% - 20px)}.hse-meta-grid,.hse-kpis,.hse-identity-grid{grid-template-columns:repeat(2,1fr)}.hse-trial-layout,.hse-output-layout{grid-template-columns:1fr}.hse-trial-detail{position:static;max-height:none}.hse-components,.hse-governance-id{grid-template-columns:repeat(2,1fr)}.hse-drawer{width:100vw}.hse-workbench{padding:12px}.hse-stage-nav{top:62px}.hse-trial-tools{grid-template-columns:1fr 1fr}.hse-grid,.hse-checks,.hse-version-grid,.hse-launch-summary,.hse-launch-grid{grid-template-columns:1fr}.hse-hypothesis dl{grid-template-columns:1fr}.hse-launch-card{align-items:flex-start;flex-wrap:wrap}.hse-launch-button{width:100%;margin-left:51px}.hse-launch-dialog{width:calc(100vw - 20px)}.hse-launch-head,.hse-launch-body,.hse-launch-actions{padding-left:15px;padding-right:15px}}
620
+ @media(max-width:520px){.hse-hero{min-height:auto;padding:22px}.hse-hero h1{font-size:30px}.hse-stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:100%}.hse-stat{min-width:0}.hse-launch-card{display:grid;grid-template-columns:38px minmax(0,1fr) 108px;align-items:center;flex-wrap:nowrap}.hse-launch-copy span{display:none}.hse-launch-button{width:100%;margin:0;padding:9px;white-space:normal}.hse-launch-button-full{display:none}.hse-launch-button-short{display:inline}}
544
621
  @media(prefers-reduced-motion:reduce){.hse-spin,.hse-status:before,.hse-hero:after{animation:none}.hse-job{transition:none}.hse-job:hover{transform:none}}
545
622
  @media(max-width:900px){.hse-report-compare,.hse-meta-flow{grid-template-columns:1fr}.hse-meta-flow div:after{display:none}}
546
623
  `;
@@ -1026,6 +1103,128 @@ function Workbench({ job, workspace, jobs, close, t }) {
1026
1103
  else content = stage === "integration" ? /* @__PURE__ */ import_react.default.createElement(ContractPanel, { artifacts, component, t }) : /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-section" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-components" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-component" }, /* @__PURE__ */ import_react.default.createElement("span", null, stage, component?.reward_affecting ? " \xB7 reward-affecting" : ""), /* @__PURE__ */ import_react.default.createElement("b", null, component?.id ?? "\u2014", " \xB7 ", component?.version ?? "\u2014"), /* @__PURE__ */ import_react.default.createElement("code", null, short(component?.digest)))));
1027
1104
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-overlay", role: "presentation", onMouseDown: (event) => event.target === event.currentTarget && close() }, /* @__PURE__ */ import_react.default.createElement("aside", { className: "hse-drawer", role: "dialog", "aria-modal": "true", "aria-label": job }, /* @__PURE__ */ import_react.default.createElement("header", { className: "hse-drawer-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", null, job), /* @__PURE__ */ import_react.default.createElement("p", null, historical ? `${t("historicalTarget")} \xB7 ${target.source_kind ?? activeJob?.generationSource?.kind ?? "\u2014"} \xB7 ${target.record_count ?? activeJob?.nTrials ?? 0} ${t("generationRecords")}` : `${activeJob?.candidate?.candidate_id ?? "\u2014"} \xB7 ${activeJob?.candidate?.version ?? "\u2014"}`, " \xB7 ", activeJob?.mode ?? "\u2014", " \xB7 ", activeJob?.progress?.completed ?? 0, "/", activeJob?.progress?.total ?? 0)), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-close", onClick: close }, t("close"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-workbench" }, /* @__PURE__ */ import_react.default.createElement("nav", { className: "hse-stage-nav", "aria-label": t("stageNav") }, STAGES.map((item) => /* @__PURE__ */ import_react.default.createElement("button", { type: "button", key: item, "data-active": stage === item, "aria-current": stage === item ? "step" : void 0, onClick: () => setStage(item) }, STAGES.indexOf(item) + 1, ". ", historical && item === "candidate" ? t("historicalTarget") : historical && item === "dataset" ? t("generationRecords") : t(item)))), state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("loading")) : state.status === "error" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-error" }, state.error, /* @__PURE__ */ import_react.default.createElement("br", null), /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-button", onClick: () => void load() }, t("retry"))) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, !contextSupported ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-capability" }, t("capabilityUnavailable")) : null, content, /* @__PURE__ */ import_react.default.createElement("details", { className: "hse-section hse-audit" }, /* @__PURE__ */ import_react.default.createElement("summary", null, t("audit"), " / ", t("artifacts")), /* @__PURE__ */ import_react.default.createElement("pre", null, pretty({ validation: detail.validation, registry: artifacts.registry, context: artifacts.context, doctor: artifacts.doctor })))))));
1028
1105
  }
1106
+ function historicalError(value) {
1107
+ const message = value?.message ?? String(value ?? "");
1108
+ const code = value?.code ?? message.match(/\b([A-Z][A-Z0-9_]{3,})\b/)?.[1] ?? "HISTORICAL_JOB_FAILED";
1109
+ return { code, message: message.replace(new RegExp(`^${code}:\\s*`), "") };
1110
+ }
1111
+ function historicalErrorHint(code, t) {
1112
+ if (code === "NO_ELIGIBLE_SESSIONS") return t("noEligibleHint");
1113
+ if (code === "SESSION_SELECTION_TOO_EXPENSIVE") return t("narrowScanHint");
1114
+ if (/SESSION_(?:SAMPLE|FEEDBACK)_CHANGED|WORKSPACE_MISMATCH|TOKEN_(?:INVALID|EXPIRED)|PREVIEW_(?:INVALID|WORKSPACE_MISMATCH)/.test(code)) return t("changedSessionHint");
1115
+ return t("historicalGenericError");
1116
+ }
1117
+ function HistoricalLauncher({ snapshot, reload, onCompleted, t }) {
1118
+ const [state, setState] = (0, import_react.useState)({ status: "idle" });
1119
+ const [open, setOpen] = (0, import_react.useState)(false);
1120
+ const workspace = snapshot?.workspace?.id;
1121
+ const operationId = state.operation?.operationId;
1122
+ (0, import_react.useEffect)(() => {
1123
+ let alive = true;
1124
+ setState({ status: "idle" });
1125
+ setOpen(false);
1126
+ if (!workspace) return () => {
1127
+ alive = false;
1128
+ };
1129
+ void api("historical-operation", { workspace }).then((operation) => {
1130
+ if (alive && ["queued", "running"].includes(operation?.status)) {
1131
+ setState({ status: "running", operation });
1132
+ }
1133
+ }).catch(() => {
1134
+ });
1135
+ return () => {
1136
+ alive = false;
1137
+ };
1138
+ }, [workspace]);
1139
+ (0, import_react.useEffect)(() => {
1140
+ if (!workspace || !operationId || !["queued", "running"].includes(state.operation?.status)) return void 0;
1141
+ let alive = true;
1142
+ let timer;
1143
+ const poll = async () => {
1144
+ try {
1145
+ const operation = await api("historical-operation", { workspace, operationId });
1146
+ if (!alive) return;
1147
+ if (operation.status === "completed") {
1148
+ setState({ status: "completed", operation });
1149
+ setOpen(false);
1150
+ await reload(true);
1151
+ if (alive) onCompleted(operation);
1152
+ return;
1153
+ }
1154
+ if (operation.status === "failed") {
1155
+ setState({ status: "error", error: historicalError(operation.error), operation });
1156
+ setOpen(true);
1157
+ return;
1158
+ }
1159
+ setState({ status: "running", operation });
1160
+ } catch {
1161
+ }
1162
+ if (alive) timer = window.setTimeout(() => void poll(), 2e3);
1163
+ };
1164
+ timer = window.setTimeout(() => void poll(), 1e3);
1165
+ return () => {
1166
+ alive = false;
1167
+ window.clearTimeout(timer);
1168
+ };
1169
+ }, [workspace, operationId, state.operation?.status, reload, onCompleted]);
1170
+ (0, import_react.useEffect)(() => {
1171
+ if (!open) return void 0;
1172
+ const escape = (event) => {
1173
+ if (event.key !== "Escape") return;
1174
+ setOpen(false);
1175
+ if (!["running", "starting"].includes(state.status)) setState({ status: "idle" });
1176
+ };
1177
+ window.addEventListener("keydown", escape);
1178
+ return () => window.removeEventListener("keydown", escape);
1179
+ }, [open, state.status]);
1180
+ const preview = async (days) => {
1181
+ setOpen(true);
1182
+ setState({ status: "previewing" });
1183
+ try {
1184
+ const value = await mutate("historical-preview", {
1185
+ workspace,
1186
+ limit: 10,
1187
+ includeFeedback: true,
1188
+ ...days ? { createdAfter: new Date(Date.now() - days * 864e5).toISOString() } : {}
1189
+ });
1190
+ setState({ status: "ready", preview: value });
1191
+ } catch (error) {
1192
+ setState({ status: "error", error: historicalError(error) });
1193
+ }
1194
+ };
1195
+ const confirm = async () => {
1196
+ if (!state.preview) return;
1197
+ setState((current) => ({ ...current, status: "starting" }));
1198
+ try {
1199
+ const operation = await mutate("historical-run", { workspace, previewId: state.preview.previewId });
1200
+ setState({ status: "running", operation });
1201
+ } catch (error) {
1202
+ const normalized = historicalError(error);
1203
+ if (normalized.code === "HISTORICAL_JOB_ALREADY_RUNNING") {
1204
+ try {
1205
+ const operation = await api("historical-operation", { workspace });
1206
+ if (["queued", "running"].includes(operation?.status)) {
1207
+ setState({ status: "running", operation });
1208
+ return;
1209
+ }
1210
+ } catch {
1211
+ }
1212
+ }
1213
+ setState({ status: "error", error: normalized });
1214
+ }
1215
+ };
1216
+ const close = () => {
1217
+ setOpen(false);
1218
+ if (!["running", "starting"].includes(state.status)) setState({ status: "idle" });
1219
+ };
1220
+ const previewValue = state.preview;
1221
+ const evaluator = previewValue?.evaluation?.evaluator;
1222
+ const judge = previewValue?.evaluation?.judge;
1223
+ const active = ["running", "starting"].includes(state.status);
1224
+ const buttonLabel = active ? t("historicalActive") : state.status === "previewing" ? t("historicalPreparing") : t("historicalLaunch");
1225
+ const buttonShort = active ? t("historicalActiveShort") : state.status === "previewing" ? t("historicalPreparingShort") : t("historicalLaunchShort");
1226
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-launch-card", "aria-live": "polite" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-mark", "aria-hidden": "true" }, "\u2726"), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-copy" }, /* @__PURE__ */ import_react.default.createElement("b", null, active ? t("historicalRunning") : t("historicalLaunch")), /* @__PURE__ */ import_react.default.createElement("span", null, active ? t("historicalRunningHint") : t("historicalLaunchBody")), /* @__PURE__ */ import_react.default.createElement("small", null, active ? `${state.operation?.selectedCount ?? "\u2014"} Trials` : t("historicalLaunchHint"))), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-launch-button", disabled: !workspace || state.status === "previewing", onClick: () => active ? setOpen(true) : void preview() }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-launch-button-full" }, buttonLabel), /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-launch-button-short" }, buttonShort))), open ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-overlay", role: "presentation", onMouseDown: (event) => event.target === event.currentTarget && close() }, /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-launch-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "hse-historical-title" }, /* @__PURE__ */ import_react.default.createElement("header", { className: "hse-launch-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("historicalLaunchHint")), /* @__PURE__ */ import_react.default.createElement("h2", { id: "hse-historical-title" }, state.status === "running" ? t("historicalRunning") : t("historicalPreviewTitle")), /* @__PURE__ */ import_react.default.createElement("p", null, state.status === "running" ? t("historicalRunningHint") : t("historicalPreviewHint"))), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-dialog-close", "aria-label": t("close"), onClick: close }, "\xD7")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-body" }, state.status === "previewing" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("historicalPreparing")) : null, state.status === "starting" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("historicalStarting")) : null, state.status === "running" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-run-state" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), /* @__PURE__ */ import_react.default.createElement("b", null, t("historicalRunning")), /* @__PURE__ */ import_react.default.createElement("span", null, state.operation?.selectedCount ?? "\u2014", " Trials \xB7 ", snapshot.workspace.label), /* @__PURE__ */ import_react.default.createElement("p", null, t("historicalRunningHint"))) : null, state.status === "completed" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-run-state" }, /* @__PURE__ */ import_react.default.createElement("b", null, "\u2713 ", t("historicalCompleted"))) : null, state.status === "error" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-error" }, /* @__PURE__ */ import_react.default.createElement("b", null, state.error.code), /* @__PURE__ */ import_react.default.createElement("p", null, state.error.message), /* @__PURE__ */ import_react.default.createElement("span", null, historicalErrorHint(state.error.code, t))) : null, state.status === "ready" && previewValue ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-summary" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("selectedSessions")), /* @__PURE__ */ import_react.default.createElement("b", null, previewValue.selected.length)), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("requestEstimate")), /* @__PURE__ */ import_react.default.createElement("b", null, previewValue.estimatedJudgeRequests)), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("tokenExpiry")), /* @__PURE__ */ import_react.default.createElement("b", null, new Date(previewValue.expiresAt).toLocaleTimeString())), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("workspace")), /* @__PURE__ */ import_react.default.createElement("b", null, snapshot.workspace.label))), /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-launch-section" }, /* @__PURE__ */ import_react.default.createElement("h3", null, t("recentSessions")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-session-list" }, previewValue.selected.map((session) => /* @__PURE__ */ import_react.default.createElement("article", { key: session.trialId }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("b", null, session.title), /* @__PURE__ */ import_react.default.createElement("span", null, session.lastActivityAt ? new Date(session.lastActivityAt).toLocaleString() : "\u2014")), /* @__PURE__ */ import_react.default.createElement("p", null, t("turnCounts"), " ", session.turnCount ?? 0, " \xB7 ", t("toolCounts"), " ", session.toolCallCount ?? 0, " \xB7 ", t("feedbackCounts"), " +", session.feedback?.positive ?? 0, " / -", session.feedback?.negative ?? 0), /* @__PURE__ */ import_react.default.createElement("code", null, (session.modelRoutes ?? []).map((route) => `${route.provider}/${route.model}`).join(" \xB7 ") || session.agentPreset || "\u2014"))))), /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-launch-section" }, /* @__PURE__ */ import_react.default.createElement("h3", null, t("historicalBoundaries")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-launch-grid" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("generatorRole")), /* @__PURE__ */ import_react.default.createElement("b", null, t("generatorRoleValue"))), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("evaluatorIdentity")), /* @__PURE__ */ import_react.default.createElement("b", null, evaluator?.id ?? "\u2014", " \xB7 ", evaluator?.version ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("judgeIdentity")), /* @__PURE__ */ import_react.default.createElement("b", null, judge?.provider ?? "\u2014", " / ", judge?.model ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("coupling")), /* @__PURE__ */ import_react.default.createElement("b", null, previewValue.evaluation?.coupling ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", null, t("evidenceRetention")), /* @__PURE__ */ import_react.default.createElement("b", null, previewValue.retention?.privateEvidence, " \xB7 ", previewValue.retention?.jobEvidence))), /* @__PURE__ */ import_react.default.createElement("p", { className: "hse-boundary-note" }, t("historicalBoundaryDetail")))) : null), /* @__PURE__ */ import_react.default.createElement("footer", { className: "hse-launch-actions" }, state.status === "ready" ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: close }, t("cancel")), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-confirm", onClick: () => void confirm() }, t("historicalConfirm"))) : null, state.status === "error" ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: close }, t("close")), state.error.code === "SESSION_SELECTION_TOO_EXPENSIVE" ? /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: () => void preview(30) }, t("recent30Days")) : /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: () => void preview() }, t("previewAgain"))) : null, state.status === "running" ? /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: close }, t("close")) : null))) : null);
1227
+ }
1029
1228
  function DashboardView({ t }) {
1030
1229
  const [workspace, setWorkspace] = (0, import_react.useState)("");
1031
1230
  const [offset, setOffset] = (0, import_react.useState)(0);
@@ -1043,7 +1242,11 @@ function DashboardView({ t }) {
1043
1242
  setWorkspace(snapshot.workspace.id);
1044
1243
  setSelected({ job, workspace: snapshot.workspace.id });
1045
1244
  };
1046
- return /* @__PURE__ */ import_react.default.createElement("main", { className: "hse-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-page" }, /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-hero", style: { "--ocean-image": `url(${harbor_ocean_default})` } }, /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-refresh", onClick: () => void state.load() }, t("refresh")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-eyebrow" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-whale", "aria-hidden": "true" }, "\u{1F433}"), t("eyebrow")), /* @__PURE__ */ import_react.default.createElement("h1", null, t("heroTitle")), /* @__PURE__ */ import_react.default.createElement("p", null, t("heroBody")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stats" }, stats.map(([label, value]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stat", key: label }, /* @__PURE__ */ import_react.default.createElement("span", null, label), /* @__PURE__ */ import_react.default.createElement("b", null, value))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", null, t("jobs")), /* @__PURE__ */ import_react.default.createElement("p", null, t("jobsHint"))), snapshot?.workspaces?.length ? /* @__PURE__ */ import_react.default.createElement("select", { className: "hse-select", "aria-label": t("workspaceSelect"), value: snapshot.workspace?.id ?? "", onChange: switchWorkspace }, snapshot.workspaces.map((item) => /* @__PURE__ */ import_react.default.createElement("option", { value: item.id, key: item.id }, item.label, " \xB7 ", item.root))) : null), snapshot?.workspace ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-hook-state" }, /* @__PURE__ */ import_react.default.createElement("b", null, t("workspace"), ": ", snapshot.workspace.label), /* @__PURE__ */ import_react.default.createElement("br", null), snapshot.config.projectRoot, " \xB7 ", snapshot.config.jobsDir) : null, state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("loading")) : state.status === "error" && !snapshot ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-error" }, state.error, /* @__PURE__ */ import_react.default.createElement("br", null), /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-button", onClick: () => void state.load() }, t("retry"))) : !snapshot?.jobs?.length ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, t("empty")) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-list" }, snapshot.jobs.map((job) => /* @__PURE__ */ import_react.default.createElement(JobCard, { job, t, open: openJob, key: job.name }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-pager" }, /* @__PURE__ */ import_react.default.createElement("span", null, pagination.total ? `${offset + 1}\u2013${Math.min(offset + (snapshot.jobs?.length ?? 0), pagination.total)} / ${pagination.total}` : "0 / 0"), /* @__PURE__ */ import_react.default.createElement("button", { disabled: !offset, onClick: () => setOffset(Math.max(0, offset - (pagination.limit ?? 20))) }, t("previous")), /* @__PURE__ */ import_react.default.createElement("button", { disabled: !pagination.hasMore, onClick: () => setOffset(offset + (pagination.limit ?? 20)) }, t("next"))))), selected ? /* @__PURE__ */ import_react.default.createElement(Workbench, { job: selected.job, workspace: selected.workspace, jobs: snapshot.jobs, close: () => setSelected(void 0), t }) : null);
1245
+ const completedHistorical = (0, import_react.useCallback)((operation) => {
1246
+ setWorkspace(operation.workspace);
1247
+ setSelected({ job: operation.jobName, workspace: operation.workspace });
1248
+ }, []);
1249
+ return /* @__PURE__ */ import_react.default.createElement("main", { className: "hse-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-page" }, /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-hero", style: { "--ocean-image": `url(${harbor_ocean_default})` } }, /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-refresh", onClick: () => void state.load() }, t("refresh")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-eyebrow" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-whale", "aria-hidden": "true" }, "\u{1F433}"), t("eyebrow")), /* @__PURE__ */ import_react.default.createElement("h1", null, t("heroTitle")), /* @__PURE__ */ import_react.default.createElement("p", null, t("heroBody")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stats" }, stats.map(([label, value]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stat", key: label }, /* @__PURE__ */ import_react.default.createElement("span", null, label), /* @__PURE__ */ import_react.default.createElement("b", null, value))))), snapshot?.workspace ? /* @__PURE__ */ import_react.default.createElement(HistoricalLauncher, { snapshot, reload: state.load, onCompleted: completedHistorical, t }) : null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", null, t("jobs")), /* @__PURE__ */ import_react.default.createElement("p", null, t("jobsHint"))), snapshot?.workspaces?.length ? /* @__PURE__ */ import_react.default.createElement("select", { className: "hse-select", "aria-label": t("workspaceSelect"), value: snapshot.workspace?.id ?? "", onChange: switchWorkspace }, snapshot.workspaces.map((item) => /* @__PURE__ */ import_react.default.createElement("option", { value: item.id, key: item.id }, item.label, " \xB7 ", item.root))) : null), snapshot?.workspace ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-hook-state" }, /* @__PURE__ */ import_react.default.createElement("b", null, t("workspace"), ": ", snapshot.workspace.label), /* @__PURE__ */ import_react.default.createElement("br", null), snapshot.config.projectRoot, " \xB7 ", snapshot.config.jobsDir) : null, state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("loading")) : state.status === "error" && !snapshot ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-error" }, state.error, /* @__PURE__ */ import_react.default.createElement("br", null), /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-button", onClick: () => void state.load() }, t("retry"))) : !snapshot?.jobs?.length ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, t("empty")) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-list" }, snapshot.jobs.map((job) => /* @__PURE__ */ import_react.default.createElement(JobCard, { job, t, open: openJob, key: job.name }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-pager" }, /* @__PURE__ */ import_react.default.createElement("span", null, pagination.total ? `${offset + 1}\u2013${Math.min(offset + (snapshot.jobs?.length ?? 0), pagination.total)} / ${pagination.total}` : "0 / 0"), /* @__PURE__ */ import_react.default.createElement("button", { disabled: !offset, onClick: () => setOffset(Math.max(0, offset - (pagination.limit ?? 20))) }, t("previous")), /* @__PURE__ */ import_react.default.createElement("button", { disabled: !pagination.hasMore, onClick: () => setOffset(offset + (pagination.limit ?? 20)) }, t("next"))))), selected ? /* @__PURE__ */ import_react.default.createElement(Workbench, { job: selected.job, workspace: selected.workspace, jobs: snapshot.jobs ?? [], close: () => setSelected(void 0), t }) : null);
1047
1250
  }
1048
1251
  function VersionPanel({ t }) {
1049
1252
  const state = useVersionCheck();
@@ -0,0 +1,180 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
3
+ const OPERATION_RETENTION_MS = 60 * 60 * 1000
4
+
5
+ function timestamp(now) {
6
+ const value = now()
7
+ return value instanceof Date ? value : new Date(value)
8
+ }
9
+
10
+ function publicError(error) {
11
+ const raw = error instanceof Error ? error.message : String(error)
12
+ const message = raw
13
+ .replace(/(?:\/[A-Za-z0-9._ -]+){2,}/g, '[local path]')
14
+ .replace(/[A-Za-z]:\\[^\s]+/g, '[local path]')
15
+ const match = message.match(/^([A-Z][A-Z0-9_]+):\s*(.*)$/s)
16
+ return {
17
+ code: match?.[1] ?? 'HISTORICAL_JOB_FAILED',
18
+ message: match?.[2] || message || 'Historical evaluation failed',
19
+ }
20
+ }
21
+
22
+ function publicOperation(value) {
23
+ if (!value) return { status: 'idle' }
24
+ return {
25
+ schemaVersion: 1,
26
+ operationId: value.operationId,
27
+ workspace: value.workspace,
28
+ status: value.status,
29
+ selectedCount: value.selectedCount,
30
+ createdAt: value.createdAt,
31
+ ...(value.startedAt ? { startedAt: value.startedAt } : {}),
32
+ ...(value.finishedAt ? { finishedAt: value.finishedAt } : {}),
33
+ ...(value.jobName ? { jobName: value.jobName } : {}),
34
+ ...(value.error ? { error: value.error } : {}),
35
+ }
36
+ }
37
+
38
+ /** Same-origin Web orchestration for the narrow Historical Session diagnostic path. */
39
+ export class HistoricalWebController {
40
+ constructor({
41
+ service,
42
+ sessionDiagnostic,
43
+ now = () => new Date(),
44
+ randomId = () => randomUUID(),
45
+ schedule = callback => queueMicrotask(callback),
46
+ operationRetentionMs = OPERATION_RETENTION_MS,
47
+ }) {
48
+ this.service = service
49
+ this.sessionDiagnostic = sessionDiagnostic
50
+ this.now = now
51
+ this.randomId = randomId
52
+ this.schedule = schedule
53
+ this.operationRetentionMs = operationRetentionMs
54
+ this.previews = new Map()
55
+ this.operations = new Map()
56
+ this.consumedPreviews = new Map()
57
+ }
58
+
59
+ _cleanup() {
60
+ const now = timestamp(this.now).getTime()
61
+ for (const [previewId, preview] of this.previews) {
62
+ if (Date.parse(preview.expiresAt) <= now) this.previews.delete(previewId)
63
+ }
64
+ for (const [operationId, operation] of this.operations) {
65
+ if (operation.finishedAt && Date.parse(operation.finishedAt) + this.operationRetentionMs <= now) {
66
+ this.operations.delete(operationId)
67
+ }
68
+ }
69
+ for (const [previewId, operationId] of this.consumedPreviews) {
70
+ if (!this.operations.has(operationId)) this.consumedPreviews.delete(previewId)
71
+ }
72
+ }
73
+
74
+ _activeOperation(workspace) {
75
+ return [...this.operations.values()]
76
+ .filter(item => item.workspace === workspace && ['queued', 'running'].includes(item.status))
77
+ .sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0]
78
+ }
79
+
80
+ async preview(args = {}) {
81
+ this._cleanup()
82
+ const resolved = await this.service.historicalWorkspace({ workspace: args.workspace })
83
+ const previewId = this.randomId()
84
+ const identity = {
85
+ projectRoot: resolved.projectRoot,
86
+ ownerSessionId: `web-historical:${this.randomId()}`,
87
+ }
88
+ const preview = await this.sessionDiagnostic.previewWithIdentity({
89
+ limit: args.limit === undefined ? 10 : args.limit,
90
+ createdAfter: args.createdAfter,
91
+ includeFeedback: args.includeFeedback !== false,
92
+ }, identity, { config: resolved.config })
93
+ const { selectionToken, ...visible } = preview
94
+ this.previews.set(previewId, {
95
+ previewId,
96
+ workspace: resolved.workspace,
97
+ identity,
98
+ config: resolved.config,
99
+ selectionToken,
100
+ selectedCount: preview.selected.length,
101
+ expiresAt: preview.expiresAt,
102
+ })
103
+ return {
104
+ ...visible,
105
+ schemaVersion: 1,
106
+ previewId,
107
+ workspace: resolved.workspace,
108
+ }
109
+ }
110
+
111
+ async run(args = {}) {
112
+ this._cleanup()
113
+ const previewId = String(args.previewId ?? '')
114
+ if (!previewId) throw new Error('HISTORICAL_PREVIEW_REQUIRED: preview the recent Sessions before confirming the Job')
115
+ const existingOperationId = this.consumedPreviews.get(previewId)
116
+ if (existingOperationId) {
117
+ const existing = this.operations.get(existingOperationId)
118
+ if (args.workspace && existing?.workspace !== args.workspace) {
119
+ throw new Error('HISTORICAL_PREVIEW_WORKSPACE_MISMATCH: the workspace changed; preview again')
120
+ }
121
+ return publicOperation(existing)
122
+ }
123
+ const preview = this.previews.get(previewId)
124
+ if (!preview) throw new Error('HISTORICAL_PREVIEW_INVALID: this preview expired or was already discarded; preview again')
125
+ if (args.workspace && preview.workspace !== args.workspace) {
126
+ throw new Error('HISTORICAL_PREVIEW_WORKSPACE_MISMATCH: the workspace changed; preview again')
127
+ }
128
+ const active = this._activeOperation(preview.workspace)
129
+ if (active) {
130
+ throw new Error('HISTORICAL_JOB_ALREADY_RUNNING: wait for the current Historical Session Job to finish')
131
+ }
132
+ const operationId = this.randomId()
133
+ const createdAt = timestamp(this.now).toISOString()
134
+ const operation = {
135
+ operationId,
136
+ workspace: preview.workspace,
137
+ status: 'queued',
138
+ selectedCount: preview.selectedCount,
139
+ createdAt,
140
+ }
141
+ this.previews.delete(previewId)
142
+ this.operations.set(operationId, operation)
143
+ this.consumedPreviews.set(previewId, operationId)
144
+ this.schedule(() => { void this._execute(operation, preview) })
145
+ return publicOperation(operation)
146
+ }
147
+
148
+ async _execute(operation, preview) {
149
+ operation.status = 'running'
150
+ operation.startedAt = timestamp(this.now).toISOString()
151
+ try {
152
+ const result = await this.sessionDiagnostic.runWithIdentity({
153
+ selectionToken: preview.selectionToken,
154
+ }, preview.identity, { config: preview.config })
155
+ operation.status = 'completed'
156
+ operation.jobName = String(result.job ?? '').split(/[\\/]/).filter(Boolean).at(-1)
157
+ if (!operation.jobName) throw new Error('HISTORICAL_JOB_INCOMPLETE: the completed run returned no Job identity')
158
+ } catch (error) {
159
+ operation.status = 'failed'
160
+ operation.error = publicError(error)
161
+ } finally {
162
+ operation.finishedAt = timestamp(this.now).toISOString()
163
+ }
164
+ }
165
+
166
+ operation(args = {}) {
167
+ this._cleanup()
168
+ const operationId = String(args.operationId ?? '')
169
+ if (operationId) {
170
+ const operation = this.operations.get(operationId)
171
+ if (!operation) return { status: 'idle' }
172
+ if (args.workspace && operation.workspace !== args.workspace) {
173
+ throw new Error('HISTORICAL_OPERATION_WORKSPACE_MISMATCH: the operation belongs to another workspace')
174
+ }
175
+ return publicOperation(operation)
176
+ }
177
+ if (!args.workspace) return { status: 'idle' }
178
+ return publicOperation(this._activeOperation(String(args.workspace)))
179
+ }
180
+ }
package/lib/service.js CHANGED
@@ -181,6 +181,15 @@ export class EvolutionService {
181
181
  }, args)
182
182
  }
183
183
 
184
+ async historicalWorkspace(args = {}) {
185
+ const { config } = await this._webContext(args)
186
+ return {
187
+ workspace: config.workspaceId,
188
+ projectRoot: path.resolve(config.projectRoot),
189
+ config: { ...config },
190
+ }
191
+ }
192
+
184
193
  async version(args = {}) {
185
194
  let config = this.config
186
195
  if (args.workspace) ({ config } = await this._webContext(args))
@@ -28,6 +28,16 @@ function executionIdentity(exec) {
28
28
  return { projectRoot: path.resolve(header.cwd), ownerSessionId: header.id }
29
29
  }
30
30
 
31
+ function normalizedIdentity(identity) {
32
+ if (typeof identity?.projectRoot !== 'string' || !path.isAbsolute(identity.projectRoot)) {
33
+ throw new Error('HISTORICAL_EXECUTION_IDENTITY_INVALID: projectRoot must be an absolute directory')
34
+ }
35
+ if (typeof identity.ownerSessionId !== 'string' || !identity.ownerSessionId) {
36
+ throw new Error('HISTORICAL_EXECUTION_IDENTITY_INVALID: ownerSessionId is required')
37
+ }
38
+ return { projectRoot: path.resolve(identity.projectRoot), ownerSessionId: identity.ownerSessionId }
39
+ }
40
+
31
41
  function feedbackItems(result) {
32
42
  return result?.ok === true && Array.isArray(result.value?.items) ? result.value.items : []
33
43
  }
@@ -147,7 +157,11 @@ export class SessionDiagnosticService {
147
157
  }
148
158
 
149
159
  async preview(args = {}, exec) {
150
- const identity = executionIdentity(exec)
160
+ return this.previewWithIdentity(args, executionIdentity(exec), { signal: exec?.signal })
161
+ }
162
+
163
+ async previewWithIdentity(args = {}, requestedIdentity, { signal, config = this.config } = {}) {
164
+ const identity = normalizedIdentity(requestedIdentity)
151
165
  const sessionQuery = capability(this.ctx, 'sessionQuery')
152
166
  const limit = args.limit ?? 10
153
167
  const createdAfter = parseCreatedAfter(args.createdAfter)
@@ -156,10 +170,10 @@ export class SessionDiagnosticService {
156
170
  projectRoot: identity.projectRoot,
157
171
  currentSessionId: identity.ownerSessionId,
158
172
  limit,
159
- maxSessionReads: this.config.sessionMaxReads ?? 100,
160
- concurrency: this.config.sessionReadConcurrency ?? 4,
173
+ maxSessionReads: config.sessionMaxReads ?? 100,
174
+ concurrency: config.sessionReadConcurrency ?? 4,
161
175
  createdAfter,
162
- signal: exec?.signal,
176
+ signal,
163
177
  })
164
178
  if (!result.selected.length) {
165
179
  throw new Error('NO_ELIGIBLE_SESSIONS: no completed top-level DSH Sessions with direct human input and assistant output were found in this workspace')
@@ -218,7 +232,7 @@ export class SessionDiagnosticService {
218
232
  evaluation,
219
233
  retention: {
220
234
  privateEvidence: '.harbor/private/session-batches',
221
- jobEvidence: this.config.jobsDir ?? 'jobs',
235
+ jobEvidence: config.jobsDir ?? 'jobs',
222
236
  vcsPolicy: 'an ignore-all file is created only when .harbor/private/.gitignore is absent; existing private rules and jobs retention/VCS policy remain project-owned',
223
237
  },
224
238
  confirmation: `Run 1 historical-generation-evaluation Job with ${selected.length} immutable Trial(s) using ${evaluation.evaluator.id}@${evaluation.evaluator.version} and Judge ${evaluation.judge.provider}/${evaluation.judge.model} (${evaluation.coupling}); no Candidate will be executed or promoted.`,
@@ -226,7 +240,11 @@ export class SessionDiagnosticService {
226
240
  }
227
241
 
228
242
  async run(args = {}, exec) {
229
- const identity = executionIdentity(exec)
243
+ return this.runWithIdentity(args, executionIdentity(exec))
244
+ }
245
+
246
+ async runWithIdentity(args = {}, requestedIdentity, { config = this.config } = {}) {
247
+ const identity = normalizedIdentity(requestedIdentity)
230
248
  if (args.stackPath !== undefined) {
231
249
  throw new Error('HISTORICAL_CUSTOM_STACK_UNSUPPORTED: the first release binds the materialized Broker Evaluator and Stack as one immutable unit')
232
250
  }
@@ -294,7 +312,7 @@ export class SessionDiagnosticService {
294
312
  observations,
295
313
  })
296
314
  const result = await this.runHistoricalEvaluation(
297
- { ...this.config, projectRoot: identity.projectRoot },
315
+ { ...config, projectRoot: identity.projectRoot },
298
316
  {
299
317
  batchPath: written.batchPath,
300
318
  batchDir: written.batchDir,
package/lib/web.js CHANGED
@@ -10,6 +10,9 @@ export const EVALUATOR_ROUTE = '/_dsh/harbor-evolution/evaluator'
10
10
  export const META_ROUTE = '/_dsh/harbor-evolution/meta'
11
11
  export const PROJECT_ROOT_ROUTE = '/_dsh/harbor-evolution/project-root'
12
12
  export const VERSION_ROUTE = '/_dsh/harbor-evolution/version'
13
+ export const HISTORICAL_PREVIEW_ROUTE = '/_dsh/harbor-evolution/historical-preview'
14
+ export const HISTORICAL_RUN_ROUTE = '/_dsh/harbor-evolution/historical-run'
15
+ export const HISTORICAL_OPERATION_ROUTE = '/_dsh/harbor-evolution/historical-operation'
13
16
  const MAX_MUTATION_BYTES = 256 * 1024
14
17
 
15
18
  function sendJson(response, status, body) {
@@ -104,7 +107,7 @@ export function createMutationHandler(update, code = 'update-failed') {
104
107
  }
105
108
  }
106
109
 
107
- export function installDashboardWeb(ctx, service) {
110
+ export function installDashboardWeb(ctx, service, historicalController) {
108
111
  if (typeof ctx.inject !== 'function') return
109
112
  ctx.inject(['webServer'], (webCtx) => {
110
113
  const routes = [
@@ -118,6 +121,11 @@ export function installDashboardWeb(ctx, service) {
118
121
  [GOVERNANCE_ROUTE, createApiHandler(args => service.governance(args), 'governance-unavailable')],
119
122
  [EVALUATOR_ROUTE, createMutationHandler(args => service.evaluator(args), 'evaluator-update-failed')],
120
123
  [META_ROUTE, createApiHandler(args => service.meta(args), 'meta-evaluation-unavailable')],
124
+ ...(historicalController ? [
125
+ [HISTORICAL_PREVIEW_ROUTE, createMutationHandler(args => historicalController.preview(args), 'historical-preview-failed')],
126
+ [HISTORICAL_RUN_ROUTE, createMutationHandler(args => historicalController.run(args), 'historical-run-failed')],
127
+ [HISTORICAL_OPERATION_ROUTE, createApiHandler(args => historicalController.operation(args), 'historical-operation-unavailable')],
128
+ ] : []),
121
129
  [VERSION_ROUTE, createApiHandler(args => service.version(args), 'version-check-unavailable')],
122
130
  [PROJECT_ROOT_ROUTE, createMutationHandler(args => service.setProjectRoot(args), 'project-root-update-failed')],
123
131
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-harbor-evolution",
3
- "version": "0.8.0",
4
- "description": "DeepSeek Harness plugin and bundled Skill for safely evolving Cordis Candidates with Harbor.",
3
+ "version": "0.8.2",
4
+ "description": "DeepSeek Harness plugin and Skill for Harbor Candidate and Historical Session evaluation workflows.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {