@stravica-ai/digital-operator-pa 0.1.47 → 0.1.49
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/dist/engineering-pa-cli/src/pa/cli.mjs +24 -1
- package/dist/engineering-pa-core/docs/commands/onboard.md +81 -7
- package/dist/engineering-pa-core/src/distill/cli.mjs +65 -0
- package/dist/engineering-pa-core/src/distill/health.mjs +322 -0
- package/dist/engineering-pa-scaffold/package.json +3 -2
- package/dist/engineering-pa-scaffold/src/commands/init-workspace-extension.mjs +37 -5
- package/dist/engineering-pa-scaffold/src/commands/init.mjs +7 -1
- package/dist/engineering-pa-scaffold/src/generator.mjs +220 -11
- package/dist/engineering-pa-workspace/app/assets/theme.css +3 -2
- package/dist/engineering-pa-workspace/app/assets/theme.js +29 -6
- package/dist/engineering-pa-workspace/app/banner.js +14 -2
- package/dist/engineering-pa-workspace/app/delegations.html +1 -1
- package/dist/engineering-pa-workspace/app/home.html +1 -1
- package/dist/engineering-pa-workspace/app/index.html +1 -1
- package/dist/engineering-pa-workspace/app/knowledge-map.html +1 -1
- package/dist/engineering-pa-workspace/app/knowledge.html +1 -1
- package/dist/engineering-pa-workspace/app/notes.html +1 -1
- package/dist/engineering-pa-workspace/app/onboarding.html +1 -1
- package/dist/engineering-pa-workspace/app/profile.html +1 -1
- package/dist/engineering-pa-workspace/app/project-detail.html +1 -1
- package/dist/engineering-pa-workspace/app/projects.html +1 -1
- package/dist/engineering-pa-workspace/app/repos.html +1 -1
- package/dist/engineering-pa-workspace/app/schedules.html +1 -1
- package/dist/engineering-pa-workspace/app/secrets.html +1 -1
- package/dist/engineering-pa-workspace/app/staging.html +1 -1
- package/dist/engineering-pa-workspace/app/tasks.html +1 -1
- package/dist/engineering-pa-workspace/app/viewer.html +1 -1
- package/dist/engineering-pa-workspace/index.mjs +16 -0
- package/dist/engineering-pa-workspace/package.json +3 -2
- package/dist/engineering-pa-workspace/src/repo-root.mjs +26 -1
- package/dist/engineering-pa-workspace/src/server.mjs +4 -3
- package/dist/engineering-pa-workspace/src/workspace-page-contract.mjs +98 -0
- package/dist/engineering-pa-workspace/src/ws.mjs +2 -0
- package/docs/onboarding-solo.md +4 -4
- package/package.json +2 -1
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
// kind: 'dir' (mkdir -p) | 'file' (write file with contents) | 'json'
|
|
8
8
|
// (stringify + write).
|
|
9
9
|
|
|
10
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
11
11
|
import { readFile, readdir } from 'node:fs/promises';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
-
import {
|
|
13
|
+
import { hostname, platform } from 'node:os';
|
|
14
|
+
import { delimiter as pathDelimiter, dirname, join, resolve } from 'node:path';
|
|
14
15
|
import { fileURLToPath } from 'node:url';
|
|
15
16
|
|
|
16
17
|
import { emptyEnvelope, renderEnvelope } from '../../engineering-pa-core/src/autonomy/envelope.mjs';
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_CADENCE_HOURS,
|
|
20
|
+
distillationScheduleFilename,
|
|
21
|
+
distillationScheduleId,
|
|
22
|
+
} from '../../engineering-pa-core/src/distill/health.mjs';
|
|
17
23
|
|
|
18
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
25
|
const requireFromHere = createRequire(import.meta.url);
|
|
@@ -553,6 +559,13 @@ export function buildOnboardingStateJson(config) {
|
|
|
553
559
|
profileIntake: 'pending',
|
|
554
560
|
aliasOffer: 'pending',
|
|
555
561
|
workspaceOffer: 'pending',
|
|
562
|
+
// Mandatory — no opt-out. The distillation sweep is the only thing
|
|
563
|
+
// that turns staged observations into knowledge docs; without an
|
|
564
|
+
// installed schedule the whole self-learning loop is dead. The
|
|
565
|
+
// /onboard walkthrough runs `scheduler install` on the scaffolded
|
|
566
|
+
// schedule file and triggers one real run before it marks this
|
|
567
|
+
// step ok. See packages/core/docs/commands/onboard.md.
|
|
568
|
+
librarianSchedule: 'pending',
|
|
556
569
|
atlassianOffer: 'pending',
|
|
557
570
|
},
|
|
558
571
|
completedAt: null,
|
|
@@ -567,10 +580,180 @@ export function buildGitkeep() {
|
|
|
567
580
|
return '';
|
|
568
581
|
}
|
|
569
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Pick the OS-native scheduler backend the scaffold writes into the
|
|
585
|
+
* distillation-sweep schedule file. macOS -> launchd, Linux -> cron,
|
|
586
|
+
* Windows -> schtasks. Every other platform lands on `null`, which
|
|
587
|
+
* makes `scheduler install` fall back to detect + refusal (with a
|
|
588
|
+
* clean stderr message).
|
|
589
|
+
*
|
|
590
|
+
* Kept as a switch on `process.platform` rather than a call to
|
|
591
|
+
* scheduler/detect.mjs: the schedule file is written once at scaffold
|
|
592
|
+
* time and shipped in the repo, so the backend choice must be stable
|
|
593
|
+
* (a `detect()` reading brew-installed cron vs launchd is a per-host
|
|
594
|
+
* moment-in-time decision that should not be baked into a tracked
|
|
595
|
+
* file). Every host later runs `scheduler install --force-host` from
|
|
596
|
+
* its own real backend; the schedule file's `backend` field just names
|
|
597
|
+
* the default. Exported for tests.
|
|
598
|
+
*
|
|
599
|
+
* @param {string} [plat] - override for tests; defaults to `process.platform`.
|
|
600
|
+
* @returns {'launchd'|'cron'|'schtasks'|null}
|
|
601
|
+
*/
|
|
602
|
+
export function defaultSchedulerBackend(plat = platform()) {
|
|
603
|
+
if (plat === 'darwin') return 'launchd';
|
|
604
|
+
if (plat === 'linux') return 'cron';
|
|
605
|
+
if (plat === 'win32') return 'schtasks';
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// System paths the launchd defaults already carry (see
|
|
610
|
+
// packages/core/src/scheduler/install.mjs withLaunchdDefaults). If
|
|
611
|
+
// `claude` resolves inside any of these, no explicit PATH is needed on
|
|
612
|
+
// the scaffolded schedule — the installer's baked-in PATH covers it.
|
|
613
|
+
const LAUNCHD_DEFAULT_PATHS = [
|
|
614
|
+
'/opt/homebrew/bin',
|
|
615
|
+
'/usr/local/bin',
|
|
616
|
+
'/usr/bin',
|
|
617
|
+
'/bin',
|
|
618
|
+
'/usr/sbin',
|
|
619
|
+
'/sbin',
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Walk `process.env.PATH` at scaffold time looking for the `claude`
|
|
624
|
+
* executable. Returns the resolved directory, or null if nothing on
|
|
625
|
+
* PATH matches. The scaffold uses this to decide whether to bake an
|
|
626
|
+
* explicit `environment.PATH` into the schedule file (needed when the
|
|
627
|
+
* operator's claude install lives somewhere the launchd default PATH
|
|
628
|
+
* would not find — e.g. ~/.local/bin from a curl-installer). Exported
|
|
629
|
+
* for tests, which drive it with a fake PATH.
|
|
630
|
+
*
|
|
631
|
+
* @param {object} [opts]
|
|
632
|
+
* @param {string} [opts.pathEnv] - override for tests; defaults to process.env.PATH.
|
|
633
|
+
* @param {string} [opts.binName] - override for tests; defaults to `claude`.
|
|
634
|
+
* @returns {string|null}
|
|
635
|
+
*/
|
|
636
|
+
export function resolveClaudeBinDir({ pathEnv, binName = 'claude' } = {}) {
|
|
637
|
+
const raw = pathEnv ?? process.env.PATH ?? '';
|
|
638
|
+
const dirs = raw.split(pathDelimiter).filter(Boolean);
|
|
639
|
+
for (const dir of dirs) {
|
|
640
|
+
const candidate = join(dir, binName);
|
|
641
|
+
let st;
|
|
642
|
+
try {
|
|
643
|
+
st = statSync(candidate);
|
|
644
|
+
} catch {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (!st.isFile()) continue;
|
|
648
|
+
if ((st.mode & 0o111) === 0) continue;
|
|
649
|
+
return dir;
|
|
650
|
+
}
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Build the operator repo's scaffolded `schedules/<paName>-distillation-sweep.json`.
|
|
656
|
+
*
|
|
657
|
+
* This is the single load-bearing artefact that makes the whole
|
|
658
|
+
* self-learning loop work in a solo instance. Without a scheduled
|
|
659
|
+
* sweep, `agents/<paName>/staging/` fills up forever and no knowledge
|
|
660
|
+
* doc is ever distilled. The `/onboard` walkthrough runs
|
|
661
|
+
* `scheduler install` on this file (with --force-host to survive the
|
|
662
|
+
* macOS hostname flap) as a mandatory step before onboarding is marked
|
|
663
|
+
* complete.
|
|
664
|
+
*
|
|
665
|
+
* Shape choices, with the estate lore they close:
|
|
666
|
+
* - `command: node node_modules/@stravica-ai/digital-operator-pa/bin/distill run`.
|
|
667
|
+
* Absolute node path is resolved by the scheduler at install time
|
|
668
|
+
* (`resolveLaunchdArgv0('node', ...)` returns `process.execPath`);
|
|
669
|
+
* the bin path goes through the stable `node_modules/@stravica-ai/…`
|
|
670
|
+
* symlink rather than the versioned `.pnpm` store path, so a
|
|
671
|
+
* package upgrade does not silently break the schedule (Dex lore
|
|
672
|
+
* w-2026-07-16-006).
|
|
673
|
+
* - `workingDirectory` is the absolute `targetDir`. No `${DAVE_ROOT}`
|
|
674
|
+
* token — a solo instance is not the estate and does not have that
|
|
675
|
+
* env var; a fresh `os.hostname()`-bound scheduler install with an
|
|
676
|
+
* unset DAVE_ROOT would otherwise fall through to a bogus
|
|
677
|
+
* `node_modules`-rooted path (`validateWorkDir` refuses that at
|
|
678
|
+
* install time — good, but the scaffold should not create that
|
|
679
|
+
* failure mode in the first place).
|
|
680
|
+
* - `host` is the current machine's hostname as `os.hostname()`
|
|
681
|
+
* computes it, so `scheduler install` without `--force-host` picks
|
|
682
|
+
* up the file cleanly on the scaffolded machine, and refuses on any
|
|
683
|
+
* other. The /onboard step passes `--force-host` unconditionally
|
|
684
|
+
* because macOS hostnames flap between the DHCP name and the
|
|
685
|
+
* configured name (w018 hostname-flap precedent), and a well-formed
|
|
686
|
+
* refusal at first-run onboarding is the worst possible operator
|
|
687
|
+
* experience.
|
|
688
|
+
* - `enabled: true`. This is not opt-in.
|
|
689
|
+
* - `schedule: '0 * / 4 * * *'` — every 4 hours, on the hour. Matches
|
|
690
|
+
* the estate's original distillation-sweep cadence and cheap enough
|
|
691
|
+
* that a solo instance never notices the tick.
|
|
692
|
+
* - `environment.PATH` is included ONLY when `claude` resolves at
|
|
693
|
+
* scaffold time OUTSIDE the launchd default PATH set (e.g. ~/.local/bin
|
|
694
|
+
* from the curl installer). In the common brew case it's covered by
|
|
695
|
+
* `withLaunchdDefaults()` and setting an explicit PATH would
|
|
696
|
+
* override — not merge — that default and lose the standard
|
|
697
|
+
* directories (w-2026-08-05 EnvironmentVariables trap).
|
|
698
|
+
*
|
|
699
|
+
* @param {object} config
|
|
700
|
+
* @param {string} config.paName
|
|
701
|
+
* @param {string} config.targetDir - absolute
|
|
702
|
+
* @param {object} [seams]
|
|
703
|
+
* @param {string} [seams.host] - hostname override (for tests)
|
|
704
|
+
* @param {'launchd'|'cron'|'schtasks'|null} [seams.backend] - override (for tests)
|
|
705
|
+
* @param {string|null} [seams.claudeBinDir] - override (for tests)
|
|
706
|
+
* @returns {object}
|
|
707
|
+
*/
|
|
708
|
+
export function buildDistillationScheduleJson(config, seams = {}) {
|
|
709
|
+
const backend = seams.backend !== undefined ? seams.backend : defaultSchedulerBackend();
|
|
710
|
+
const host = seams.host !== undefined ? seams.host : hostname();
|
|
711
|
+
const claudeBinDir = seams.claudeBinDir !== undefined
|
|
712
|
+
? seams.claudeBinDir
|
|
713
|
+
: resolveClaudeBinDir();
|
|
714
|
+
|
|
715
|
+
const out = {
|
|
716
|
+
id: distillationScheduleId(config.paName),
|
|
717
|
+
agent: config.paName,
|
|
718
|
+
name: `Librarian distillation sweep (every ${DEFAULT_CADENCE_HOURS}h)`,
|
|
719
|
+
schedule: `0 */${DEFAULT_CADENCE_HOURS} * * *`,
|
|
720
|
+
command: 'node node_modules/@stravica-ai/digital-operator-pa/bin/distill run',
|
|
721
|
+
workingDirectory: config.targetDir,
|
|
722
|
+
enabled: true,
|
|
723
|
+
backend,
|
|
724
|
+
host,
|
|
725
|
+
description: [
|
|
726
|
+
`Solo-instance librarian distillation sweep for ${config.paName}. Turns the`,
|
|
727
|
+
'staged observations under agents/<paName>/staging/ into promoted knowledge',
|
|
728
|
+
'docs under operator/knowledge/, then invalidates the retrieval graph so the',
|
|
729
|
+
'next `pa knowledge search` sees the new material. Installed by the /onboard',
|
|
730
|
+
'walkthrough (mandatory step) via `scheduler install --force-host`. Removing',
|
|
731
|
+
'this schedule silently kills the PA\'s self-learning loop; reinstate it by',
|
|
732
|
+
're-running /onboard.',
|
|
733
|
+
].join(' '),
|
|
734
|
+
// DAVE_ROOT MUST be set explicitly — the scheduler's withLaunchdDefaults()
|
|
735
|
+
// falls back to `resolve(__dirname, '..','..','..')` which under a
|
|
736
|
+
// published tarball lands inside the versioned .pnpm store path and
|
|
737
|
+
// `validateLaunchdDaveRoot` refuses (exit 2) at install time. The
|
|
738
|
+
// solo colleague running `scheduler install` from their instance
|
|
739
|
+
// shell also does not typically have DAVE_ROOT set. Baking it into
|
|
740
|
+
// the schedule closes both cases (w-2026-07-15-003 + w-2026-07-16-006
|
|
741
|
+
// trap class). PATH is only added when `claude` lives outside the
|
|
742
|
+
// launchd default PATH set.
|
|
743
|
+
environment: { DAVE_ROOT: config.targetDir },
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
if (claudeBinDir && !LAUNCHD_DEFAULT_PATHS.includes(claudeBinDir)) {
|
|
747
|
+
out.environment.PATH = [claudeBinDir, ...LAUNCHD_DEFAULT_PATHS].join(pathDelimiter);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return out;
|
|
751
|
+
}
|
|
752
|
+
|
|
570
753
|
/**
|
|
571
754
|
* Build the .claude/settings.json contents.
|
|
572
755
|
*
|
|
573
|
-
*
|
|
756
|
+
* Three things:
|
|
574
757
|
* 1. `permissions.deny` for the built-in `deep-research` skill and
|
|
575
758
|
* workflow. The core operating model (Bucket 4) directs the PA to
|
|
576
759
|
* the `/research` verb, which persists a session and promotes
|
|
@@ -584,12 +767,16 @@ export function buildGitkeep() {
|
|
|
584
767
|
* scaffold ships the wiring inert. A per-repo overlay slot is
|
|
585
768
|
* scaffolded alongside as `.claude/coordinator-overlay.json`.
|
|
586
769
|
*
|
|
587
|
-
* SessionStart
|
|
588
|
-
*
|
|
589
|
-
* the
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
770
|
+
* 3. `hooks.SessionStart` wiring `distill health --hook-envelope` so
|
|
771
|
+
* every session boots with a `distillationHealth (live)` block at
|
|
772
|
+
* the top of the context. Reads the local distill ledger + the
|
|
773
|
+
* scaffolded schedule file — never spawns claude, never triggers a
|
|
774
|
+
* sweep, cheap to run. The block warns loudly when the schedule is
|
|
775
|
+
* missing (fresh scaffold pre-/onboard), disabled, or the last run
|
|
776
|
+
* is stale, so the whole self-learning loop cannot die silently
|
|
777
|
+
* the way it did on the WSD-laptop cold install of 0.1.41. Invoked
|
|
778
|
+
* through `pnpm exec` because Claude Code hook subprocesses do not
|
|
779
|
+
* inherit the pnpm-augmented PATH the interactive session enjoys.
|
|
593
780
|
*/
|
|
594
781
|
export function buildClaudeSettingsJson() {
|
|
595
782
|
return {
|
|
@@ -608,6 +795,17 @@ export function buildClaudeSettingsJson() {
|
|
|
608
795
|
],
|
|
609
796
|
},
|
|
610
797
|
],
|
|
798
|
+
SessionStart: [
|
|
799
|
+
{
|
|
800
|
+
matcher: '',
|
|
801
|
+
hooks: [
|
|
802
|
+
{
|
|
803
|
+
type: 'command',
|
|
804
|
+
command: 'pnpm exec distill health --hook-envelope',
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
],
|
|
611
809
|
},
|
|
612
810
|
};
|
|
613
811
|
}
|
|
@@ -810,12 +1008,23 @@ export function buildPlanFromTemplates(config, templates) {
|
|
|
810
1008
|
plan.push({ kind: 'file', path: `agents/${paName}/autonomy-envelope.md`, contents: autonomyEnvelope });
|
|
811
1009
|
plan.push({ kind: 'json', path: `agents/${paName}/spawn-log.json`, contents: spawnLog });
|
|
812
1010
|
|
|
813
|
-
//
|
|
1011
|
+
// Load-bearing: the scaffolded distillation-sweep schedule the
|
|
1012
|
+
// /onboard walkthrough installs and fires as a mandatory step. See
|
|
1013
|
+
// buildDistillationScheduleJson for the shape rationale.
|
|
1014
|
+
const distillSched = buildDistillationScheduleJson(config);
|
|
1015
|
+
plan.push({
|
|
1016
|
+
kind: 'json',
|
|
1017
|
+
path: `schedules/${distillationScheduleFilename(paName)}`,
|
|
1018
|
+
contents: distillSched,
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
// Empty-directory markers — git won't track empty dirs. `schedules/`
|
|
1022
|
+
// now ships with a real file (the distillation sweep) so it never
|
|
1023
|
+
// needs a .gitkeep.
|
|
814
1024
|
for (const d of [
|
|
815
1025
|
`agents/${paName}/staging`,
|
|
816
1026
|
'work',
|
|
817
1027
|
'projects',
|
|
818
|
-
'schedules',
|
|
819
1028
|
'.vault',
|
|
820
1029
|
'operator/extensions/roles',
|
|
821
1030
|
'operator/extensions/tools',
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Two themes live here: dark (default) and light, toggled by the operator
|
|
5
5
|
* via the banner button. Selection is persisted in localStorage under
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
6
|
+
* `dop.theme` (product-generic; legacy `dave.theme` is read once and
|
|
7
|
+
* migrated forward) and applied to `documentElement.dataset.theme` before
|
|
8
|
+
* first paint by the inline init script in each page's <head>.
|
|
8
9
|
*
|
|
9
10
|
* Token set is the union of every page's prior `:root` block. Both themes
|
|
10
11
|
* MUST define the same set of names — any unilateral token becomes invisible
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Two responsibilities split across two surfaces:
|
|
5
5
|
*
|
|
6
6
|
* 1. Pre-paint init. Each page also embeds a tiny inline <script> in <head>
|
|
7
|
-
* that reads `localStorage.
|
|
7
|
+
* that reads `localStorage.dop.theme` and sets
|
|
8
8
|
* `documentElement.dataset.theme` BEFORE first paint. That avoids a
|
|
9
9
|
* light-flash on a dark-preference user (FOUC). The inline script is the
|
|
10
10
|
* duplicate of `applyStoredTheme()` below; keeping it inline is the
|
|
@@ -18,7 +18,17 @@
|
|
|
18
18
|
* context (matching banner.js's behaviour) so a page opened directly
|
|
19
19
|
* still owns its own toggle.
|
|
20
20
|
*
|
|
21
|
-
* Storage: localStorage key `
|
|
21
|
+
* Storage: localStorage key `dop.theme`, values 'dark' | 'light'. The
|
|
22
|
+
* canonical token lives in `src/workspace-page-contract.mjs`; this file
|
|
23
|
+
* hand-copies the literal because it runs in the browser and cannot
|
|
24
|
+
* import ESM modules from that path. The theme parity test pins the two
|
|
25
|
+
* copies to the same value.
|
|
26
|
+
*
|
|
27
|
+
* Legacy: `dave.theme` is the pre-rename key. On first boot after the
|
|
28
|
+
* rename we read it once, copy the value to `dop.theme`, and never touch
|
|
29
|
+
* it again — nobody re-picks their theme just because the estate went
|
|
30
|
+
* product-generic. See `src/workspace-page-contract.mjs`.
|
|
31
|
+
*
|
|
22
32
|
* Default when unset: 'dark' (operator preference — see CLAUDE.md). We do
|
|
23
33
|
* NOT consult prefers-color-scheme; dark is the chosen default and we don't
|
|
24
34
|
* want auto-detection overriding it on first load.
|
|
@@ -26,15 +36,22 @@
|
|
|
26
36
|
(function () {
|
|
27
37
|
'use strict';
|
|
28
38
|
|
|
29
|
-
var STORAGE_KEY = '
|
|
39
|
+
var STORAGE_KEY = 'dop.theme';
|
|
40
|
+
var LEGACY_STORAGE_KEY = 'dave.theme';
|
|
30
41
|
var DEFAULT_THEME = 'dark';
|
|
31
42
|
var VALID = { dark: 1, light: 1 };
|
|
32
|
-
var MESSAGE_TYPE = '
|
|
43
|
+
var MESSAGE_TYPE = 'dop.theme.set';
|
|
33
44
|
|
|
34
45
|
function readStored() {
|
|
35
46
|
try {
|
|
36
47
|
var raw = localStorage.getItem(STORAGE_KEY);
|
|
37
48
|
if (raw && VALID[raw]) return raw;
|
|
49
|
+
// Legacy fallback: one-time migration from the pre-rename key.
|
|
50
|
+
var legacy = localStorage.getItem(LEGACY_STORAGE_KEY);
|
|
51
|
+
if (legacy && VALID[legacy]) {
|
|
52
|
+
try { localStorage.setItem(STORAGE_KEY, legacy); } catch (_e) { /* no-op */ }
|
|
53
|
+
return legacy;
|
|
54
|
+
}
|
|
38
55
|
} catch (_e) { /* storage unavailable — fall through to default */ }
|
|
39
56
|
return DEFAULT_THEME;
|
|
40
57
|
}
|
|
@@ -148,12 +165,18 @@
|
|
|
148
165
|
boot();
|
|
149
166
|
}
|
|
150
167
|
|
|
151
|
-
// Expose for tests + the inline init helper.
|
|
152
|
-
|
|
168
|
+
// Expose for tests + the inline init helper. `__daveTheme` is kept as an
|
|
169
|
+
// alias for anything under `operator/extensions/**` that grew a direct
|
|
170
|
+
// reference before the rename; `__dopTheme` is the canonical name and
|
|
171
|
+
// is what new callers should use.
|
|
172
|
+
var api = {
|
|
153
173
|
read: readStored,
|
|
154
174
|
write: writeStored,
|
|
155
175
|
apply: applyTheme,
|
|
156
176
|
storageKey: STORAGE_KEY,
|
|
177
|
+
legacyStorageKey: LEGACY_STORAGE_KEY,
|
|
157
178
|
defaultTheme: DEFAULT_THEME,
|
|
158
179
|
};
|
|
180
|
+
window.__dopTheme = api;
|
|
181
|
+
window.__daveTheme = api;
|
|
159
182
|
})();
|
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
|
|
23
23
|
const ENDPOINT = '/__api/status-mon';
|
|
24
24
|
const POLL_INTERVAL_MS = 30_000;
|
|
25
|
-
|
|
25
|
+
// Product-generic key; the pre-rename store was 'dave.statusmon.dismissed'.
|
|
26
|
+
// Read-once fallback keeps existing dismissals across the rename so a
|
|
27
|
+
// resolved incident someone had already dismissed doesn't reappear.
|
|
28
|
+
const DISMISSED_KEY = 'dop.statusmon.dismissed';
|
|
29
|
+
const LEGACY_DISMISSED_KEY = 'dave.statusmon.dismissed';
|
|
26
30
|
|
|
27
31
|
const SLOT_ID = 'global-banner';
|
|
28
32
|
|
|
@@ -36,7 +40,15 @@
|
|
|
36
40
|
|
|
37
41
|
function readDismissed() {
|
|
38
42
|
try {
|
|
39
|
-
|
|
43
|
+
var raw = localStorage.getItem(DISMISSED_KEY);
|
|
44
|
+
if (!raw) {
|
|
45
|
+
// Legacy fallback: read the pre-rename store once and migrate.
|
|
46
|
+
var legacy = localStorage.getItem(LEGACY_DISMISSED_KEY);
|
|
47
|
+
if (legacy) {
|
|
48
|
+
try { localStorage.setItem(DISMISSED_KEY, legacy); } catch { /* no-op */ }
|
|
49
|
+
raw = legacy;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
40
52
|
if (!raw) return [];
|
|
41
53
|
const parsed = JSON.parse(raw);
|
|
42
54
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Delegations — Workspace</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Home</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Digital Operator</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%236366f1'/%3E%3Ctext x='8' y='12' font-family='monospace' font-size='11' font-weight='bold' text-anchor='middle' fill='%23121218'%3ED%3C/text%3E%3C/svg%3E">
|
|
8
8
|
<script src="/banner.js" defer></script>
|
|
9
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
9
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
10
10
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
11
11
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
12
12
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Knowledge Map — Workspace</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Knowledge</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Notes — Dave</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Welcome to your PA</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Profile</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Project Detail</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Projects — Dave</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Git Overview — Workspace</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Schedules — Workspace</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Secrets</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Staging Inbox — Workspace</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Tasks — Dave</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Document Viewer</title>
|
|
7
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
7
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
10
10
|
<style>
|
|
@@ -10,3 +10,19 @@ export const version = '0.1.1';
|
|
|
10
10
|
export { loadNav } from './src/nav.mjs';
|
|
11
11
|
export { startPoller as startStatusMonPoller } from './src/status-mon/poller.mjs';
|
|
12
12
|
export { handleApi } from './src/api.mjs';
|
|
13
|
+
|
|
14
|
+
// Shared workspace-page contract — every core page and every scaffolded
|
|
15
|
+
// extension page carries the same head + top-of-body markup. Owning the
|
|
16
|
+
// tokens here stops the scaffold and the core pages drifting on the theme
|
|
17
|
+
// storage key.
|
|
18
|
+
export {
|
|
19
|
+
THEME_STORAGE_KEY,
|
|
20
|
+
THEME_LEGACY_STORAGE_KEY,
|
|
21
|
+
THEME_MESSAGE_TYPE,
|
|
22
|
+
THEME_CSS_LINK,
|
|
23
|
+
THEME_JS_TAG,
|
|
24
|
+
BANNER_JS_TAG,
|
|
25
|
+
GLOBAL_BANNER_HTML,
|
|
26
|
+
prepaintScriptTag,
|
|
27
|
+
standardHeadInner,
|
|
28
|
+
} from './src/workspace-page-contract.mjs';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stravica-ai/engineering-pa-workspace",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Workspace server + viewer — operator-facing local web UI for the PA platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./index.mjs"
|
|
8
|
+
".": "./index.mjs",
|
|
9
|
+
"./index.mjs": "./index.mjs"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
12
|
"@stravica-ai/engineering-pa-cli": "workspace:*",
|