codex-agent-view 0.5.5 → 0.5.6

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-agent-view",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "A read-only Codex plugin for monitoring live tasks and subagents in your browser. Open each view with one lightweight @codex-agent-view invocation. Once open, live monitoring runs locally with no additional model calls.",
5
5
  "author": {
6
6
  "name": "Junho Yoon",
package/README.ko.md CHANGED
@@ -13,19 +13,36 @@ Codex를 대체하거나 작업·에이전트를 제어하지 않으며, 외부
13
13
  일반 터미널에서 현재 릴리스를 설치합니다.
14
14
 
15
15
  ```bash
16
- npm install --global codex-agent-view@0.5.5
16
+ codex --version
17
+ ```
18
+
19
+ PATH에서 선택되는 `codex` executable을 npm으로 설치했다면 다음과 같이 업데이트한 뒤 선택된 version을 다시 확인합니다.
20
+
21
+ ```bash
22
+ npm install --global @openai/codex@latest
23
+ codex --version
24
+ ```
25
+
26
+ Codex를 다른 install channel로 설치했다면 그 channel에서 업데이트하세요. 그다음 plugin package를 설치합니다.
27
+
28
+ ```bash
29
+ npm install --global codex-agent-view@0.5.6
17
30
  codex-agent-view install
18
31
  ```
19
32
 
20
- 번째 명령은 npm package를 설치하고, 두 번째 명령은 포함된 local Codex plugin을 명시적으로 등록합니다. `npm install`만으로는 Codex 설정을 바꾸지 않으며, 이 package에는 설정을 자동 변경하는 `postinstall` script가 없습니다.
33
+ `0.5.6` installer는 등록 전에 포함된 plugin을 marketplace의 strict subdirectory로 복사하여 `0.5.5`의 marketplace-root discovery 실패를 피합니다.
34
+
35
+ npm 명령은 package를 설치합니다. `codex-agent-view install`은 포함된 local Codex plugin을 복사하고 명시적으로 등록합니다. `npm install`만으로는 Codex 설정을 바꾸지 않으며, 이 package에는 설정을 자동 변경하는 `postinstall` script가 없습니다.
21
36
 
22
37
  설치 후에는 다음 순서를 따릅니다.
23
38
 
24
- 1. 설치 중 Codex 앱이 열려 있었다면 완전히 종료한 다시 엽니다.
25
- 2. Codex 앱의 **Plugins** 화면에서 **Codex Agent View**가 설치·활성화됐는지 확인합니다.
26
- 3. Hook 검토 화면이 나오면 `hooks/hooks.json`과 `node "${PLUGIN_ROOT}/scripts/send-hook.mjs"` 명령을 확인하고 현재 정의를 명시적으로 trust합니다. 앱이 hook 검토 화면을 제공하지 않을 때만 설치 과정에서 interactive Codex CLI의 `/hooks`를 사용합니다.
39
+ 1. Codex 앱의 **Plugins** 화면에서 **Codex Agent View**가 설치·활성화됐는지 확인합니다.
40
+ 2. **Hooks** 영역에서 **검토(Review)**를 선택해 `hooks/hooks.json`과 `node "${PLUGIN_ROOT}/scripts/send-hook.mjs"` 명령을 확인한 뒤, 현재 정의에 **모두 신뢰하기(Trust all)**를 선택합니다. 앱에 Hook 검토 UI가 없을 때만 interactive Codex CLI의 `/hooks`를 사용합니다.
41
+ 3. Codex 앱을 완전히 종료한 다시 엽니다.
27
42
  4. Codex 앱에서 새 작업을 만듭니다. 설치 전에 지나간 event는 재생되지 않습니다.
28
43
 
44
+ 현재 programmatic plugin status에는 저장된 Hook 신뢰 상태가 노출되지 않으므로 browser와 `doctor`는 승인 여부를 확인할 수 없습니다. Codex 앱이나 interactive `/hooks` UI에서 직접 확인하세요.
45
+
29
46
  ## 사용
30
47
 
31
48
  Codex 앱의 작업 입력창에서 다음 plugin을 선택해 그대로 전송합니다.
@@ -40,6 +57,7 @@ Codex 앱의 작업 입력창에서 다음 plugin을 선택해 그대로 전송
40
57
  - 터미널에서 monitor를 시작하지 않습니다.
41
58
  - localhost 주소를 복사하거나 관리하지 않습니다.
42
59
  - 브라우저 탭을 닫아도 Codex 작업은 중단되지 않습니다. 새 인증 탭이 필요하면 `@codex-agent-view`를 다시 실행합니다.
60
+ - Viewer를 연 작업 자체는 화면에서 숨겨지므로, 확인하려는 작업과 별도의 viewer 작업에서 실행합니다.
43
61
 
44
62
  공개 Codex plugin API는 이 흐름을 위한 앱 sidebar나 in-app panel 자동 생성을 안정적으로 제공하지 않으므로, 운영체제 기본 브라우저가 지원하는 표시 화면입니다.
45
63
 
@@ -94,7 +112,7 @@ codex-agent-view uninstall --purge
94
112
 
95
113
  ## 릴리스 및 프로젝트 문서
96
114
 
97
- Registry digest, exact reinstall, CI, tag와 GitHub Release를 포함한 공개·검증 증거는 변경할 수 없는 npm package README 밖의 [배포 문서](docs/distribution.md)에 기록합니다.
115
+ Registry digest, 기기의 compatible-CLI reinstall, CI, tag와 GitHub Release를 포함한 공개·검증 증거는 변경할 수 없는 npm package README 밖의 [배포 문서](docs/distribution.md)에 기록합니다. 이 검증은 clean cross-device first install을 확인하지 않았으며, 위 호환성 실패는 별도 사실로 기록합니다.
98
116
 
99
117
  npm 공개와 Universal Plugins Directory 제출은 별도 절차입니다. Directory validator 호환성, portal/reviewer acceptance, 검색 노출과 promptless plugin-card Quick start 동작은 아직 확인하지 않았으며 이 문서에서 지원을 주장하지 않습니다.
100
118
 
package/README.md CHANGED
@@ -13,19 +13,36 @@ It does not replace Codex, control tasks or agents, run a hosted service, send t
13
13
  Install the current release from a regular terminal:
14
14
 
15
15
  ```bash
16
- npm install --global codex-agent-view@0.5.5
16
+ codex --version
17
+ ```
18
+
19
+ If the `codex` executable on your PATH was installed through npm, update it and verify the selected version again:
20
+
21
+ ```bash
22
+ npm install --global @openai/codex@latest
23
+ codex --version
24
+ ```
25
+
26
+ If Codex came from another install channel, update it through that channel instead. Then install the plugin package:
27
+
28
+ ```bash
29
+ npm install --global codex-agent-view@0.5.6
17
30
  codex-agent-view install
18
31
  ```
19
32
 
20
- The first command installs the npm package. The second explicitly registers its bundled local Codex plugin. `npm install` alone does not modify Codex settings, and this package has no `postinstall` script that does so.
33
+ The `0.5.6` installer copies the bundled plugin into a strict marketplace subdirectory before registration, avoiding the marketplace-root discovery failure in `0.5.5`.
34
+
35
+ The npm command installs the package. `codex-agent-view install` copies and explicitly registers its bundled local Codex plugin. `npm install` alone does not modify Codex settings, and this package has no `postinstall` script that does so.
21
36
 
22
37
  Then:
23
38
 
24
- 1. If Codex was open during installation, quit it completely and reopen it.
25
- 2. In the Codex app's **Plugins** screen, confirm that **Codex Agent View** is installed and enabled.
26
- 3. If Codex asks you to review hooks, inspect `hooks/hooks.json` and the `node "${PLUGIN_ROOT}/scripts/send-hook.mjs"` command before explicitly trusting the current definition. If the app does not provide hook review, use `/hooks` in the interactive Codex CLI during installation.
39
+ 1. In the Codex app's **Plugins** screen, confirm that **Codex Agent View** is installed and enabled.
40
+ 2. In its **Hooks** section, choose **Review**, inspect `hooks/hooks.json` and the `node "${PLUGIN_ROOT}/scripts/send-hook.mjs"` command, then choose **Trust all** for the current definitions. Only if the app has no hook-review UI, use `/hooks` in the interactive Codex CLI.
41
+ 3. Quit Codex completely and reopen it.
27
42
  4. Create a new Codex task. Events from before installation are not replayed.
28
43
 
44
+ Persisted hook trust is not exposed by the current programmatic plugin status, so the browser and `doctor` cannot confirm that approval; verify it in the Codex app or interactive `/hooks` UI.
45
+
29
46
  ## Use
30
47
 
31
48
  In a Codex app task, select and send:
@@ -40,6 +57,7 @@ That is the whole normal-use command. The plugin prepares or reuses its local mo
40
57
  - Do not start the monitor in a terminal.
41
58
  - Do not copy or manage a localhost URL.
42
59
  - Closing the browser tab does not stop Codex work. Invoke `@codex-agent-view` again when you want a new authenticated tab.
60
+ - The task used to open the viewer is hidden from the view itself, so use a separate viewer task to monitor the work you care about.
43
61
 
44
62
  The public Codex plugin API does not provide a reliable automatic app sidebar or in-app panel for this workflow, so the default browser is the supported display surface.
45
63
 
@@ -94,7 +112,7 @@ codex-agent-view uninstall --purge
94
112
 
95
113
  ## Release and project documentation
96
114
 
97
- Release publication and verification evidence is recorded outside the immutable npm package README in [Distribution](docs/distribution.md), including registry digests, exact reinstall checks, CI, tags, and GitHub Releases.
115
+ Release publication and verification evidence is recorded outside the immutable npm package README in [Distribution](docs/distribution.md), including registry digests, this-device compatible-CLI reinstall checks, CI, tags, and GitHub Releases. Those checks did not verify a clean cross-device first install; the compatibility failure above is now recorded separately.
98
116
 
99
117
  npm publication is separate from submission to the Universal Plugins Directory. Directory validator compatibility, portal/reviewer acceptance, search exposure, and promptless plugin-card Quick start behavior remain unverified and are not claimed here.
100
118
 
@@ -32,6 +32,8 @@ import {
32
32
  const PACKAGE_ROOT = resolve(fileURLToPath(new URL("../", import.meta.url)));
33
33
  const PLUGIN_ID = "codex-agent-view@codex-agent-view";
34
34
  const MARKETPLACE_NAME = "codex-agent-view";
35
+ const PLUGIN_BUNDLE_RELATIVE_PATH = join("plugins", "codex-agent-view");
36
+ const INSTALLED_MARKETPLACE_PLUGIN_SOURCE = "./plugins/codex-agent-view";
35
37
  const BUNDLE_MARKER = ".codex-agent-view-owned.json";
36
38
  const BUNDLE_MARKER_SCHEMA_VERSION = 1;
37
39
  const PREPARE_LIVE_VIEW_WAIT_MS = 2_000;
@@ -50,8 +52,7 @@ const KNOWN_PRE_PROOF_MANAGED_VERSIONS = new Set([
50
52
  ]);
51
53
  const CANONICAL_THREAD_ID_PATTERN =
52
54
  /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
53
- const INSTALL_ENTRIES = [
54
- ".agents",
55
+ const PLUGIN_INSTALL_ENTRIES = [
55
56
  ".codex-plugin",
56
57
  "assets",
57
58
  "bin",
@@ -641,13 +642,11 @@ async function inspectInstalledBundleForLiveView() {
641
642
  throw new LiveViewPreparationError("plugin_bundle_unowned");
642
643
  }
643
644
 
644
- const manifest = await readJsonRegularFile(
645
- join(destination, ".codex-plugin", "plugin.json"),
646
- );
647
- if (manifest?.name !== MARKETPLACE_NAME || typeof manifest.version !== "string") {
645
+ const installedBundle = await inspectManagedBundleLayout(destination);
646
+ if (installedBundle === null) {
648
647
  throw new LiveViewPreparationError("plugin_bundle_invalid");
649
648
  }
650
- if (manifest.version !== (await packageVersion())) {
649
+ if (installedBundle.version !== (await packageVersion())) {
651
650
  throw new LiveViewPreparationError("plugin_version_mismatch");
652
651
  }
653
652
  }
@@ -874,18 +873,46 @@ async function inspectPluginBundle(destination) {
874
873
  }
875
874
 
876
875
  const marker = await readJsonRegularFile(join(destination, BUNDLE_MARKER));
877
- const manifest = await readJsonRegularFile(
878
- join(destination, ".codex-plugin", "plugin.json"),
879
- );
880
876
  if (
881
877
  marker?.schema_version !== BUNDLE_MARKER_SCHEMA_VERSION ||
882
878
  marker?.package !== MARKETPLACE_NAME ||
883
- marker?.plugin_id !== PLUGIN_ID ||
884
- manifest?.name !== MARKETPLACE_NAME
879
+ marker?.plugin_id !== PLUGIN_ID
885
880
  ) {
886
881
  return { kind: "unmanaged" };
887
882
  }
888
- return { kind: "managed", version: manifest.version };
883
+ const installedBundle = await inspectManagedBundleLayout(destination);
884
+ if (installedBundle === null) {
885
+ return { kind: "unmanaged" };
886
+ }
887
+ return {
888
+ kind: "managed",
889
+ layout: installedBundle.layout,
890
+ version: installedBundle.version,
891
+ };
892
+ }
893
+
894
+ async function inspectManagedBundleLayout(destination) {
895
+ const candidates = [
896
+ {
897
+ layout: "subdirectory",
898
+ path: join(destination, PLUGIN_BUNDLE_RELATIVE_PATH, ".codex-plugin", "plugin.json"),
899
+ },
900
+ {
901
+ layout: "legacy-root",
902
+ path: join(destination, ".codex-plugin", "plugin.json"),
903
+ },
904
+ ];
905
+ const matches = [];
906
+ for (const candidate of candidates) {
907
+ const manifest = await readJsonRegularFile(candidate.path);
908
+ if (
909
+ manifest?.name === MARKETPLACE_NAME &&
910
+ typeof manifest.version === "string"
911
+ ) {
912
+ matches.push({ layout: candidate.layout, version: manifest.version });
913
+ }
914
+ }
915
+ return matches.length === 1 ? matches[0] : null;
889
916
  }
890
917
 
891
918
  function legacyBearerProbeOptions(bundle) {
@@ -926,10 +953,31 @@ async function copyPluginBundle(destination) {
926
953
  await rm(destination, { force: true, recursive: true });
927
954
  }
928
955
  await mkdir(destination, { mode: 0o700, recursive: true });
929
- for (const entry of INSTALL_ENTRIES) {
956
+ await cp(join(PACKAGE_ROOT, ".agents"), join(destination, ".agents"), {
957
+ errorOnExist: true,
958
+ force: false,
959
+ recursive: true,
960
+ });
961
+ const installedCatalogPath = join(destination, ".agents", "plugins", "marketplace.json");
962
+ const installedCatalog = JSON.parse(await readFile(installedCatalogPath, "utf8"));
963
+ const installedEntry = installedCatalog?.plugins?.find(
964
+ (entry) => entry?.name === MARKETPLACE_NAME,
965
+ );
966
+ if (installedEntry?.source?.source !== "local") {
967
+ throw new Error("the bundled marketplace catalog is invalid");
968
+ }
969
+ installedEntry.source.path = INSTALLED_MARKETPLACE_PLUGIN_SOURCE;
970
+ await writeFile(installedCatalogPath, `${JSON.stringify(installedCatalog, null, 2)}\n`, {
971
+ encoding: "utf8",
972
+ flag: "w",
973
+ mode: 0o600,
974
+ });
975
+ const pluginDestination = join(destination, PLUGIN_BUNDLE_RELATIVE_PATH);
976
+ await mkdir(pluginDestination, { mode: 0o700, recursive: true });
977
+ for (const entry of PLUGIN_INSTALL_ENTRIES) {
930
978
  const source = join(PACKAGE_ROOT, entry);
931
979
  if (!(await pathExists(source))) continue;
932
- await cp(source, join(destination, entry), {
980
+ await cp(source, join(pluginDestination, entry), {
933
981
  errorOnExist: true,
934
982
  force: false,
935
983
  recursive: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-agent-view",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "A read-only Codex plugin for monitoring live tasks and subagents in your browser. Open each view with one lightweight @codex-agent-view invocation. Once open, live monitoring runs locally with no additional model calls.",
5
5
  "type": "module",
6
6
  "main": "./src/core/index.mjs",
package/public/app.js CHANGED
@@ -125,10 +125,17 @@ const MESSAGES = Object.freeze({
125
125
  emptyTitle: "No task activity has been received in this observation window.",
126
126
  emptyCopy: "The local monitor is connected. This result alone does not mean that Codex has no active work or participating agents.",
127
127
  emptyGuidanceTitle: "If work does not appear",
128
+ hookReviewTitle: "Check hook approval first",
129
+ hookReviewCopy: "No activity has reached this monitor. The hook may not be trusted or loaded yet, but this page cannot inspect Codex's saved hook-trust state.",
128
130
  automaticTracking: "You do not need to enter or register task IDs. Activity from trusted hooks is added automatically.",
129
- emptyStep1: "After installing the plugin, fully restart the official Codex app.",
130
- emptyStep2: "In a new task, review and explicitly trust the Codex Agent View hook command.",
131
- emptyStep3: "After trusting it, start new work and run agents. New activity is added automatically.",
131
+ emptyStep1: "In the Codex app, open Plugins > Codex Agent View, confirm that it is enabled, choose Review under Hooks, and then choose Trust all if those controls are available.",
132
+ emptyStep2: "If the app has no hook-review controls, open the interactive Codex CLI, run /hooks, inspect the current Codex Agent View command, and approve it.",
133
+ emptyStep3: "Fully quit and restart the official Codex app so it loads the current plugin and hook definition.",
134
+ emptyStep4: "After approval and restart, create a new task and start new work to verify delivery.",
135
+ viewerTaskHiddenTitle: "The viewer task is hidden from this page.",
136
+ viewerTaskHiddenCopy: "The only observed work item is the task that opened this view, so the visible list is empty.",
137
+ viewerTaskActionTitle: "Open the viewer from a separate task",
138
+ viewerTaskExclusion: "The task that opened this view is intentionally excluded. Open Codex Agent View from a separate lightweight viewer task, then start or continue the work you want to monitor in another task.",
132
139
  observationBoundary: "Observation starts with the first trusted hook event. Earlier activity and activity missed while local collection was stopped cannot be replayed; restarting collection opens a new observation window.",
133
140
  diagnosticsCount: "Validation information · {count}",
134
141
  diagnosticOccurrences: "{count} occurrences",
@@ -280,10 +287,17 @@ const MESSAGES = Object.freeze({
280
287
  emptyTitle: "이 관찰 화면에서 수신된 작업 활동이 없습니다.",
281
288
  emptyCopy: "로컬 모니터 연결은 정상입니다. 이 결과만으로 Codex에 진행 중인 작업이나 참여 에이전트가 없다고 판단할 수 없습니다.",
282
289
  emptyGuidanceTitle: "표시되지 않을 때 확인 순서",
290
+ hookReviewTitle: "먼저 Hook 승인 상태를 확인하세요",
291
+ hookReviewCopy: "아직 이 모니터에 도착한 활동이 없습니다. Hook이 신뢰되지 않았거나 아직 로드되지 않았을 수 있지만, 이 화면에서는 Codex에 저장된 Hook 신뢰 상태를 확인할 수 없습니다.",
283
292
  automaticTracking: "작업 ID를 입력하거나 작업별로 등록할 필요가 없습니다. 신뢰한 hook의 작업 활동이 이 목록에 자동으로 추가됩니다.",
284
- emptyStep1: "플러그인을 설치한 공식 Codex 앱을 완전히 재시작했는지 확인합니다.",
285
- emptyStep2: " 작업에서 표시되는 Codex Agent View hook 명령을 검토하고 직접 신뢰합니다.",
286
- emptyStep3: "신뢰 설정 작업을 시작해 에이전트를 실행합니다. 활동은 목록에 자동으로 추가됩니다.",
293
+ emptyStep1: "Codex 앱에서 Plugins > Codex Agent View를 열어 활성화 상태를 확인하고, Hooks에서 '검토'를 누른 뒤 해당 버튼이 보이면 '모두 신뢰하기'를 선택합니다.",
294
+ emptyStep2: "앱에 Hook 검토 화면이 없다면 interactive Codex CLI에서 /hooks를 실행하고 현재 Codex Agent View 명령을 검토한 승인합니다.",
295
+ emptyStep3: "현재 플러그인과 Hook 정의를 다시 로드하도록 공식 Codex 앱을 완전히 종료한 재시작합니다.",
296
+ emptyStep4: "승인과 재시작을 마친 뒤 새 작업을 만들고 새로운 활동을 시작해 전달 여부를 확인합니다.",
297
+ viewerTaskHiddenTitle: "이 화면을 연 작업은 목록에서 숨겨집니다.",
298
+ viewerTaskHiddenCopy: "현재 관찰된 유일한 작업이 이 화면을 연 작업이므로 표시 목록이 비어 있습니다.",
299
+ viewerTaskActionTitle: "별도의 작업에서 보기 화면을 여세요",
300
+ viewerTaskExclusion: "이 화면을 연 작업은 의도적으로 제외됩니다. 별도의 가벼운 보기용 작업에서 Codex Agent View를 연 뒤, 다른 작업에서 관찰할 새 작업을 시작하거나 기존 작업을 계속하세요.",
287
301
  observationBoundary: "관찰 화면은 첫 번째로 신뢰한 hook을 받은 시점부터 시작합니다. 그 전에 이미 지나간 활동과 로컬 상태 수집이 중단된 동안의 활동은 재생되지 않으며, 수집이 다시 시작되면 새 관찰 화면이 열립니다.",
288
302
  diagnosticsCount: "검증 정보 · {count}건",
289
303
  diagnosticOccurrences: "{count}건",
@@ -435,10 +449,17 @@ const MESSAGES = Object.freeze({
435
449
  emptyTitle: "No se recibió actividad en esta ventana de observación.",
436
450
  emptyCopy: "El monitor local está conectado. Este resultado no implica por sí solo que Codex no tenga trabajos o agentes activos.",
437
451
  emptyGuidanceTitle: "Si el trabajo no aparece",
452
+ hookReviewTitle: "Comprueba primero la aprobación del hook",
453
+ hookReviewCopy: "Aún no llegó actividad a este monitor. Es posible que el hook todavía no sea confiable o no esté cargado, pero esta página no puede consultar el estado de confianza guardado por Codex.",
438
454
  automaticTracking: "No es necesario introducir ni registrar IDs de tareas. La actividad de hooks confiables se añade automáticamente.",
439
- emptyStep1: "Tras instalar el plugin, reinicia por completo la aplicación oficial de Codex.",
440
- emptyStep2: "En una tarea nueva, revisa y autoriza explícitamente el comando hook de Codex Agent View.",
441
- emptyStep3: "Después, inicia un trabajo nuevo y ejecuta agentes. La actividad se añadirá automáticamente.",
455
+ emptyStep1: "En la aplicación Codex, abre Plugins > Codex Agent View, confirma que esté activado, elige Revisar en Hooks y después Confiar en todos si aparecen esos controles.",
456
+ emptyStep2: "Si la aplicación no tiene controles para revisar hooks, abre la CLI interactiva de Codex, ejecuta /hooks, inspecciona el comando actual de Codex Agent View y apruébalo.",
457
+ emptyStep3: "Cierra por completo y reinicia la aplicación oficial de Codex para que cargue el plugin y la definición de hook actuales.",
458
+ emptyStep4: "Después de aprobar y reiniciar, crea una tarea nueva e inicia trabajo nuevo para verificar la entrega.",
459
+ viewerTaskHiddenTitle: "La tarea que abrió esta vista está oculta.",
460
+ viewerTaskHiddenCopy: "El único trabajo observado es la tarea que abrió esta vista, por eso la lista visible está vacía.",
461
+ viewerTaskActionTitle: "Abre el visor desde otra tarea",
462
+ viewerTaskExclusion: "La tarea que abrió esta vista se excluye intencionalmente. Abre Codex Agent View desde otra tarea ligera de visualización y después inicia o continúa el trabajo que quieras observar en una tarea diferente.",
442
463
  observationBoundary: "La observación comienza con el primer evento de un hook autorizado. La actividad anterior o perdida mientras la recopilación local estuvo detenida no se puede reproducir; al reiniciarla se abre una ventana nueva.",
443
464
  diagnosticsCount: "Información de validación · {count}",
444
465
  diagnosticOccurrences: "{count} apariciones",
@@ -1713,12 +1734,26 @@ function setEmptyObservationMessage() {
1713
1734
  elements.stateMessage.className = "state-message state-empty state-empty-observation";
1714
1735
  elements.stateMessage.replaceChildren();
1715
1736
 
1737
+ const rawSessions = Array.isArray(viewState.sessions) ? viewState.sessions : [];
1738
+ const hasOnlyExcludedViewerTask =
1739
+ rawSessions.length > 0 &&
1740
+ observableSessions().length === 0 &&
1741
+ CANONICAL_SESSION_ID_PATTERN.test(excludedSessionId) &&
1742
+ rawSessions.some((session) => session.sessionId === excludedSessionId);
1743
+ const noHookEventsObserved =
1744
+ viewState.diagnostics.length === 0 &&
1745
+ rawSessions.length === 0 &&
1746
+ (!Number.isFinite(viewState.updatedAtMs) || viewState.updatedAtMs <= 0);
1747
+
1716
1748
  const heading = document.createElement("strong");
1717
1749
  const copy = document.createElement("span");
1718
1750
 
1719
1751
  if (viewState.diagnostics.length) {
1720
1752
  heading.textContent = t("emptyWithDiagnosticsTitle");
1721
1753
  copy.textContent = t("emptyWithDiagnosticsCopy", { count: viewState.diagnostics.length });
1754
+ } else if (hasOnlyExcludedViewerTask) {
1755
+ heading.textContent = t("viewerTaskHiddenTitle");
1756
+ copy.textContent = t("viewerTaskHiddenCopy");
1722
1757
  } else {
1723
1758
  heading.textContent = t("emptyTitle");
1724
1759
  copy.textContent = t("emptyCopy");
@@ -1734,18 +1769,45 @@ function setEmptyObservationMessage() {
1734
1769
  automaticTracking.className = "automatic-tracking";
1735
1770
  automaticTracking.textContent = t("automaticTracking");
1736
1771
 
1737
- const steps = document.createElement("ol");
1738
- for (const step of [t("emptyStep1"), t("emptyStep2"), t("emptyStep3")]) {
1739
- const item = document.createElement("li");
1740
- item.textContent = step;
1741
- steps.append(item);
1742
- }
1743
-
1744
1772
  const boundary = document.createElement("p");
1745
1773
  boundary.className = "observation-boundary";
1746
1774
  boundary.textContent = t("observationBoundary");
1747
1775
 
1748
- guidance.append(guidanceTitle, automaticTracking, steps, boundary);
1776
+ guidance.append(guidanceTitle);
1777
+ if (hasOnlyExcludedViewerTask) {
1778
+ const viewerTaskCallout = document.createElement("section");
1779
+ viewerTaskCallout.className = "viewer-task-callout";
1780
+ const viewerTaskTitle = document.createElement("h4");
1781
+ viewerTaskTitle.textContent = t("viewerTaskActionTitle");
1782
+ const viewerTaskCopy = document.createElement("p");
1783
+ viewerTaskCopy.textContent = t("viewerTaskExclusion");
1784
+ viewerTaskCallout.append(viewerTaskTitle, viewerTaskCopy);
1785
+ guidance.append(viewerTaskCallout);
1786
+ } else if (noHookEventsObserved) {
1787
+ const hookReview = document.createElement("section");
1788
+ hookReview.className = "hook-review-callout";
1789
+ const hookReviewTitle = document.createElement("h4");
1790
+ hookReviewTitle.textContent = t("hookReviewTitle");
1791
+ const hookReviewCopy = document.createElement("p");
1792
+ hookReviewCopy.textContent = t("hookReviewCopy");
1793
+ hookReview.append(hookReviewTitle, hookReviewCopy);
1794
+
1795
+ const steps = document.createElement("ol");
1796
+ for (const step of [
1797
+ t("emptyStep1"),
1798
+ t("emptyStep2"),
1799
+ t("emptyStep3"),
1800
+ t("emptyStep4"),
1801
+ ]) {
1802
+ const item = document.createElement("li");
1803
+ item.textContent = step;
1804
+ steps.append(item);
1805
+ }
1806
+ guidance.append(hookReview, automaticTracking, steps);
1807
+ } else {
1808
+ guidance.append(automaticTracking);
1809
+ }
1810
+ guidance.append(boundary);
1749
1811
 
1750
1812
  if (viewState.diagnostics.length) {
1751
1813
  const diagnostics = document.createElement("div");
package/public/styles.css CHANGED
@@ -482,6 +482,29 @@ h1 {
482
482
  font-size: var(--text-sm);
483
483
  }
484
484
 
485
+ .hook-review-callout,
486
+ .viewer-task-callout {
487
+ margin-bottom: var(--space-4);
488
+ padding: var(--space-4);
489
+ background: color-mix(in srgb, var(--accent) 9%, var(--surface-raised));
490
+ border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--border-default));
491
+ border-left: 0.3rem solid var(--accent);
492
+ border-radius: var(--radius-sm);
493
+ }
494
+
495
+ .hook-review-callout h4,
496
+ .viewer-task-callout h4 {
497
+ margin-bottom: var(--space-2);
498
+ font-size: var(--text-md);
499
+ }
500
+
501
+ .hook-review-callout p,
502
+ .viewer-task-callout p {
503
+ margin: 0;
504
+ color: var(--text-secondary);
505
+ font-size: var(--text-sm);
506
+ }
507
+
485
508
  .automatic-tracking {
486
509
  margin-bottom: var(--space-3);
487
510
  color: var(--text-secondary);