From 8d1d0a46dd2f88bcb33de1b23f545d3afa4493ea Mon Sep 17 00:00:00 2001 From: Dominique Devereaux <90852374+ibloud@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:53:44 -0500 Subject: [PATCH 01/40] feat(companion): recover independent module and scoped task continuity Preserve the existing guide application/build/deployment. Recover reachable Companion source from the preserved ee59555 reconciliation, add a standalone runner and explicit portable task contracts, and record remaining acceptance and mirror/plugin gates. Source review only; no runtime migration or production deployment. --- COMPANION-HOSTING-STATUS.md | 9 + README.md | 10 + apps/companion/.gitignore | 5 + apps/companion/README.md | 151 + apps/companion/app/account.tsx | 43 + apps/companion/app/api/companion/route.ts | 8 + apps/companion/app/companion.tsx | 81 + apps/companion/app/creative-help.tsx | 50 + apps/companion/app/device-context.tsx | 14 + apps/companion/app/globals.css | 84 + apps/companion/app/layout.tsx | 4 + apps/companion/app/mcp/route.ts | 12 + apps/companion/app/page.tsx | 2 + apps/companion/app/pixie-helper.tsx | 15 + apps/companion/app/task-steward.tsx | 77 + apps/companion/lib/browser-context.mjs | 10 + apps/companion/lib/checkpoint-pointer.mjs | 12 + apps/companion/lib/companion-request.ts | 9 + apps/companion/lib/companion.mjs | 117 + apps/companion/lib/creative-help.mjs | 49 + apps/companion/lib/pixie-guide.mjs | 21 + apps/companion/lib/principles.mjs | 19 + apps/companion/lib/project-connections.mjs | 53 + apps/companion/lib/projects.json | 276 + apps/companion/lib/site-access.mjs | 7 + apps/companion/lib/task-contract.mjs | 76 + apps/companion/package.json | 70 + apps/companion/pnpm-lock.yaml | 9288 +++++++++++++++++ apps/companion/postcss.config.mjs | 7 + apps/companion/public/favicon.svg | 6 + apps/companion/public/file.svg | 1 + apps/companion/public/globe.svg | 1 + .../public/oauth-client-metadata.json | 19 + .../public/shared/atproto-account.js | 115 + .../public/shared/atproto-sign-in.js | 96 + apps/companion/public/window.svg | 1 + apps/companion/review/browser-results.json | 42 + apps/companion/scripts/mirror-tangled.mjs | 91 + apps/companion/server.mjs | 57 + apps/companion/source-recovery.json | 304 + apps/companion/static/index.html | 2 + apps/companion/static/main.tsx | 5 + apps/companion/tests/browser-context.test.mjs | 15 + apps/companion/tests/browser-contract.cjs | 55 + apps/companion/tests/companion.test.mjs | 52 + apps/companion/tests/creative-help.test.mjs | 39 + .../tests/independent-server.test.mjs | 32 + apps/companion/tests/mirror-tangled.test.mjs | 57 + .../tests/project-connections.test.mjs | 48 + apps/companion/tests/task-contract.test.mjs | 35 + apps/companion/tsconfig.json | 40 + apps/companion/vite.tangled.config.ts | 11 + 52 files changed, 11703 insertions(+) create mode 100644 apps/companion/.gitignore create mode 100644 apps/companion/README.md create mode 100644 apps/companion/app/account.tsx create mode 100644 apps/companion/app/api/companion/route.ts create mode 100644 apps/companion/app/companion.tsx create mode 100644 apps/companion/app/creative-help.tsx create mode 100644 apps/companion/app/device-context.tsx create mode 100644 apps/companion/app/globals.css create mode 100644 apps/companion/app/layout.tsx create mode 100644 apps/companion/app/mcp/route.ts create mode 100644 apps/companion/app/page.tsx create mode 100644 apps/companion/app/pixie-helper.tsx create mode 100644 apps/companion/app/task-steward.tsx create mode 100644 apps/companion/lib/browser-context.mjs create mode 100644 apps/companion/lib/checkpoint-pointer.mjs create mode 100644 apps/companion/lib/companion-request.ts create mode 100644 apps/companion/lib/companion.mjs create mode 100644 apps/companion/lib/creative-help.mjs create mode 100644 apps/companion/lib/pixie-guide.mjs create mode 100644 apps/companion/lib/principles.mjs create mode 100644 apps/companion/lib/project-connections.mjs create mode 100644 apps/companion/lib/projects.json create mode 100644 apps/companion/lib/site-access.mjs create mode 100644 apps/companion/lib/task-contract.mjs create mode 100644 apps/companion/package.json create mode 100644 apps/companion/pnpm-lock.yaml create mode 100644 apps/companion/postcss.config.mjs create mode 100644 apps/companion/public/favicon.svg create mode 100644 apps/companion/public/file.svg create mode 100644 apps/companion/public/globe.svg create mode 100644 apps/companion/public/oauth-client-metadata.json create mode 100644 apps/companion/public/shared/atproto-account.js create mode 100644 apps/companion/public/shared/atproto-sign-in.js create mode 100644 apps/companion/public/window.svg create mode 100644 apps/companion/review/browser-results.json create mode 100644 apps/companion/scripts/mirror-tangled.mjs create mode 100644 apps/companion/server.mjs create mode 100644 apps/companion/source-recovery.json create mode 100644 apps/companion/static/index.html create mode 100644 apps/companion/static/main.tsx create mode 100644 apps/companion/tests/browser-context.test.mjs create mode 100644 apps/companion/tests/browser-contract.cjs create mode 100644 apps/companion/tests/companion.test.mjs create mode 100644 apps/companion/tests/creative-help.test.mjs create mode 100644 apps/companion/tests/independent-server.test.mjs create mode 100644 apps/companion/tests/mirror-tangled.test.mjs create mode 100644 apps/companion/tests/project-connections.test.mjs create mode 100644 apps/companion/tests/task-contract.test.mjs create mode 100644 apps/companion/tsconfig.json create mode 100644 apps/companion/vite.tangled.config.ts diff --git a/COMPANION-HOSTING-STATUS.md b/COMPANION-HOSTING-STATUS.md index 2b07c07..bc4b258 100644 --- a/COMPANION-HOSTING-STATUS.md +++ b/COMPANION-HOSTING-STATUS.md @@ -5,3 +5,12 @@ pixie.loptrlab.com is the public guide and contributor entrance, deployed from t The interactive companion remains at https://pixie-project-companion.ibloud-ivxx.chatgpt.site/ . Its server endpoints and MCP integration have not migrated. GitHub Pages cannot execute them. Do not describe the public guide as the migrated interactive runtime or as shared memory across projects. A later migration must preserve project data and tool behavior, replace host-specific authentication, test anonymous contributor access and MCP authorization, and verify the replacement before switching links or retiring the current backend. No new hosting service, DNS change, or backend deployment was introduced by this button update. + +## Independent application source candidate — 26 October 2026 + +Owner direction selects this existing repository as the preferred GitHub source +home for a separate `apps/companion` module. The guide build and current public +runtime links are unchanged. See the module README for recovered source lineage, +independent build/HTTP/MCP commands, local verification and outstanding clean-install, +authentication, accessibility, hosting and mirror acceptance. No deployed runtime, +plugin endpoint, OAuth origin, source mirror or domain has migrated by this review. diff --git a/README.md b/README.md index 16624ff..724223e 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,13 @@ Use this existing GitHub repository as PIXIE’s public guide/front door. Keep t working Project Companion Site and plugin identity in place during acceptance. See [HOSTING-AND-DNS.md](HOSTING-AND-DNS.md) for the exact DNS record, verified deployment blocker and separation between source development and server hosting. + +## Companion application source review + +The owner selected this existing repository as the preferred source home for a +separate Companion module, subject to preserving this guide. The recovered, +independently runnable application is under [apps/companion](apps/companion/README.md). +Its build, tests and Node/MCP runner are independent of the root guide build. +This source review does not deploy the module, migrate the existing Site/plugin, +change domains or establish mirror synchronization. Follow the module's explicit +acceptance and Site-retirement gates; the guide's existing functionality remains. diff --git a/apps/companion/.gitignore b/apps/companion/.gitignore new file mode 100644 index 0000000..cf5c6fe --- /dev/null +++ b/apps/companion/.gitignore @@ -0,0 +1,5 @@ +node_modules +tangled-site/ +*.tsbuildinfo +.env* +review-evidence/ diff --git a/apps/companion/README.md b/apps/companion/README.md new file mode 100644 index 0000000..a7aabea --- /dev/null +++ b/apps/companion/README.md @@ -0,0 +1,151 @@ +# PIXIE Project Companion — independent source review + +This is the recovered Companion application module inside the existing PIXIE +guide repository. It is executable source, not a replacement guide or a released +hosting migration. The guide's root application, build and Pages workflow remain +separate and unchanged. Existing ChatGPT Site/App/plugin identities remain live. + +## Source and ownership + +Owner direction on 26 October 2026 selected this repository, subject to preserving +the guide, as the preferred existing GitHub home. This review module was recovered +from local reconciled source `ee595556dfd352fdcb6342ea5de20b6688f79ff8`. +That history retains Sites v17 `73ef3b57` and Tangled's `2214950e` / `90287b0d`. +`source-recovery.json` distinguishes preserved, adapted and new files by hash. +No private Site bindings, credentials, caches, unrelated starter components or +private participant records were imported. The original checkouts and archives +are retained. No new license or rights transfer is implied by source availability. + +The task contract and independent Node runner are new implementation. Shared +project/resource operations, contributor checkpoints, identity UI, styles and +mirror safeguards are recovered and extended, rather than recreated. Retained +`app/api/companion` and `app/mcp` files document the original Sites adapters; +`server.mjs` supplies the independent HTTP adapters. Neither trusts the other's +authentication mechanism implicitly. + +## Build and run outside Sites + +Requires Node >=22.13 and the pinned pnpm version in package.json. + +```sh +cd apps/companion +pnpm install --frozen-lockfile +pnpm test +pnpm run typecheck +pnpm run build +pnpm start +``` + +The runner defaults to `127.0.0.1:4173`. `HOST` and `PORT` select an explicitly +approved deployment's bind address; changing them does not provision or deploy a +service. The existing Vite build writes `tangled-site/`, ignored generated output. +The guide's root build does not copy or publish this module. + +This review built and type-checked outside Sites with available pinned +dependencies. A separate clean offline install was attempted and blocked because +the package mirror lacked supply-chain metadata (`ERR_PNPM_NO_OFFLINE_META` for +`@alloc/quick-lru`). No policy bypass, lockfile rewrite or false clean-install +claim was made. Verify a fresh network-enabled checkout/install before acceptance. +The recovered dependency declarations/lock remain intact apart from application +name and runnable script adaptation; unused starter dependency reduction is not +silently represented as completed. + +## HTTP and MCP boundaries + +- `/`: independent browser workspace. Find help, Project work and Task stewardship + share the existing service functions. Browser-static mode executes validated + read/draft operations locally; external public-source reads still require + provider availability and CORS. No private database or automatic posting. +- `POST /api/companion`: same supported read/draft operations, same-origin browser + checks, bounded request body and no server task persistence. +- `POST /mcp`: shared `handleRpc`/`runTool` operations with independent bearer + authentication. Configure `PIXIE_MCP_TOKEN` privately with at least 32 characters; + never commit or embed it in browser code. Without it MCP fails closed with 503; + invalid credentials return 401. Fake Sites identity headers do not authorize it. +- `/health`: reports runner mode and whether MCP authentication is configured; + never reveals a credential. A health response is not release acceptance. + +This minimal bearer adapter is a tested transport boundary, not a completed +multi-user OAuth installation flow. Real ChatGPT/other-client connection and +per-client authorization/revocation require review before public service release. +Do not expose an unconfigured endpoint or treat web identity sign-in as MCP auth. +The existing AT Protocol client metadata remains bound to the published Site; +another origin offers the existing sign-in link rather than claiming migrated +OAuth. Do not retire the Site while that dependency remains. + +## Supported operations: read versus draft versus execution + +| Operations | Behavior | +| --- | --- | +| list_projects, get_project_status, discover_resources, get_resource, resume_checkpoint | Read curated/public sources; retain dates and retrieval limitations. | +| suggest_contribution, prepare_feedback, prepare_checkpoint, prepare_enquiry | Prepare proposals/drafts. GitHub submission and professional contact remain user actions. | +| export_project_handoff, prepare_followup, resume_followup | Prepare/validate explicit portable context. No automatic synchronization or server storage. | +| prepare_task_contract, update_task_contract, resume_task_contract | Maintain explicit task context; scope fields stay fixed during updates. Imported context requires fresh review. | +| check_task_action | Reports in-scope/out-of-scope/approval requirement. Executes nothing and grants no credentials. | + +No supported operation publishes, deploys, merges, sends correspondence, spends, +deletes, grants repository access or approves canon. A scope check cannot constrain +unrelated tools in a host, and an editable file is not cryptographic authorization. +The companion's own task updates cannot silently change outcome, project, actions, +constraints or completion criteria. A new scope requires a new explicitly reviewed +task. Reported completion requires evidence but is not verified completion. + +## Portable continuity and privacy + +Task stewardship records outcome, supported actions, constraints, completion +criteria, status, progress, next action and evidence. Download/import preserves +task ID and revision. Edits must update the working contract before export, and +every import/update resets the public-safe review gate. Changing the selected +project does not silently discard or reassign the working task. + +Only export public-safe material. Working text remains in tab memory; downloaded +files belong to the user. Existing Remember/Forget retains only a public GitHub +issue pointer on that device. GitHub issues remain the shared contribution record. +No opaque chat memory, cross-domain draft transfer or synchronization is claimed. +MCP clients/hosting providers have their own retention. Do not enter credentials, +private briefs, health/benefits records or confidential correspondence. + +## Tests and accessibility + +`pnpm test` covers existing project/resource/checkpoint behavior, task round-trips, +scope rejection, forged imports, completion evidence, independent HTTP/MCP parity, +fail-closed authentication and the existing mirror safety fixtures. + +`pnpm run check:browser` uses a separately available Playwright/Chromium runner; +set `PIXIE_PLAYWRIGHT_MODULE` to its module path, `PIXIE_CHROMIUM` to the browser +executable, and `PIXIE_BASE_URL` to the local runner. It does not deploy or submit +anything. Evidence defaults to ignored `review-evidence/`. Browser tooling is not +an application runtime dependency. + +Physical iPad Safari/Files, VoiceOver, a desktop screen reader, clean install, +independent live-source checks and a separate contributor's plugin installation +remain acceptance gates. Never substitute a build, viewport emulation or an +automated contrast check for those observations. Preserve existing identities, +styles, explicit consent and source-first behavior in future changes. + +## Existing plugin and Tangled mirror + +The installed plugin remains the existing owner-identified 1.0.4 release; the +local historical public-submission manifest says 1.0.3 and is not a verified new +release. Installed tools still expose the previous catalog; these new task tools +are source candidates, not installed changes. Plugin search did not return an +editable PIXIE release ID. No replacement plugin or guessed release was created. + +The existing public Tangled mirror was read without mutation at +`https://tangled.org/made-sick.org/pixie-project-companion`: main advertised +`90287b0d64475df606667078d9bb98bbfc07651a`, with no tags in that response. +It is NOT synchronized with this new GitHub module. The source roots differ: +this repository contains the guide plus `apps/companion`; the existing mirror +contains the older Companion at its root. Do not push the whole guide repository +through the retained mirror helper, force-update the mirror, activate automation, +or invent a second mirror. Agree and verify a history-preserving module mapping +under separate authorization after source review. Until then report not synced. + +## Release and Site-retirement gate + +Do not merge/deploy this module, change domains, repoint the existing plugin or +delete the Site merely because local tests pass. First verify clean installation, +independent hosted behavior, live source access, accessibility, auth/client update, +mirror mapping and every Site-bound dependency. Record exact source/build/client +and human acceptance evidence. Source recovery, review publication, deployment, +plugin update and Site retirement are distinct actions. diff --git a/apps/companion/app/account.tsx b/apps/companion/app/account.tsx new file mode 100644 index 0000000..f0fc3a3 --- /dev/null +++ b/apps/companion/app/account.tsx @@ -0,0 +1,43 @@ +'use client'; +import {useEffect,useRef,useState} from 'react'; + +type IdentityUI = {destroy:()=>void}; +declare global { + interface Window { PixieIdentity?: {mount:(element:HTMLElement)=>IdentityUI}; } +} +const loads = new Map>(); +function loadScript(src:string) { + if (!loads.has(src)) loads.set(src,new Promise((resolve,reject)=>{ + const script=document.createElement('script');script.src=src;script.async=true; + script.onload=()=>resolve();script.onerror=()=>{loads.delete(src);script.remove();reject(new Error('Account tools could not load.'));}; + document.head.appendChild(script); + })); + return loads.get(src)!; +} +export default function Account(){ + const panel=useRef(null);const details=useRef(null);const [error,setError]=useState('');const [connected,setConnected]=useState(false); + useEffect(()=>{ + let disposed=false,ui:IdentityUI|undefined,observer:MutationObserver|undefined; + const open=()=>{if(window.location.hash==='#account'&&details.current)details.current.open=true;};open(); + window.addEventListener('hashchange',open); + loadScript('./shared/atproto-account.js?v=1').then(()=>loadScript('./shared/atproto-sign-in.js?v=1')).then(()=>{ + if(!disposed&&panel.current){ + ui=window.PixieIdentity?.mount(panel.current); + const form=panel.current.querySelector('form'); + if(form){observer=new MutationObserver(()=>setConnected(form.hidden));observer.observe(form,{attributes:true,attributeFilter:['hidden']});setConnected(form.hidden);} + } + }).catch(()=>{if(!disposed)setError('Account tools could not load. You can continue without signing in; reload to try again.');}); + const closeOutside=(event:PointerEvent)=>{if(details.current&&!details.current.contains(event.target as Node))details.current.open=false;}; + const escape=(event:KeyboardEvent)=>{if(event.key==='Escape'&&!event.defaultPrevented&&details.current?.open&&!panel.current?.querySelector('[aria-expanded="true"]')){details.current.open=false;details.current.querySelector('summary')?.focus();}}; + document.addEventListener('pointerdown',closeOutside);document.addEventListener('keydown',escape); + return()=>{disposed=true;observer?.disconnect();ui?.destroy();window.removeEventListener('hashchange',open);document.removeEventListener('pointerdown',closeOutside);document.removeEventListener('keydown',escape);}; + },[]); + return
+ {connected?'Account':'Sign in'} +

Your atmosphere account

+ {error&&

{error}

} +
+ +
+
; +} diff --git a/apps/companion/app/api/companion/route.ts b/apps/companion/app/api/companion/route.ts new file mode 100644 index 0000000..84f9cd0 --- /dev/null +++ b/apps/companion/app/api/companion/route.ts @@ -0,0 +1,8 @@ +import { runTool } from '../../../lib/companion.mjs'; +export async function POST(request:Request){ + const origin=request.headers.get('origin'); + if(origin&&origin!==new URL(request.url).origin)return Response.json({error:'Invalid origin'},{status:403}); + const raw=await request.text();if(raw.length>16000)return Response.json({error:'Request too large'},{status:413}); + try{const {name,args}=JSON.parse(raw);return Response.json(await runTool(name,args),{headers:{'Cache-Control':'no-store'}});} + catch(e){return Response.json({error:e instanceof Error?e.message:'Invalid request'},{status:400});} +} diff --git a/apps/companion/app/companion.tsx b/apps/companion/app/companion.tsx new file mode 100644 index 0000000..8350e2f --- /dev/null +++ b/apps/companion/app/companion.tsx @@ -0,0 +1,81 @@ +'use client'; +import {useEffect,useState} from 'react'; +import {checkpointPointer,readCheckpointPointer,checkpointStorageKey} from '../lib/checkpoint-pointer.mjs'; +import projects from '../lib/projects.json'; +import {projectConnections} from '../lib/project-connections.mjs'; +import CreativeHelp from './creative-help'; +import Account from './account'; +import {companionRequest} from '../lib/companion-request'; +import PixieHelper from './pixie-helper'; +import DeviceContext from './device-context'; +import TaskSteward from './task-steward'; +import {principles,accessStatus} from '../lib/principles.mjs'; +type Result = Record; +export default function Companion(){ + const [handoffNotice,setHandoffNotice]=useState(''); + const [view,setView]=useState('help'); + useEffect(()=>{const sync=()=>setView(window.location.hash==='#task'?'tasks':window.location.hash==='#workspace'?'projects':'help');sync();window.addEventListener('hashchange',sync);return()=>window.removeEventListener('hashchange',sync);},[]); + const [project,setProject]=useState('training');const [mode,setMode]=useState('contribute');const [minutes,setMinutes]=useState(15); + const [result,setResult]=useState(null);const [busy,setBusy]=useState(false);const [error,setError]=useState('');const [copied,setCopied]=useState(false); + const [form,setForm]=useState({summary:'',steps:'',expected:'',actual:'',environment:'',build:''}); + const [checkpoint,setCheckpoint]=useState({task:'',progress:'',blocker:'',next_action:''});const [issueUrl,setIssueUrl]=useState(''); + const [remembered,setRemembered]=useState<{version:number;projectId:string;issueUrl:string}|null>(null);const [storageNotice,setStorageNotice]=useState(''); + useEffect(()=>{try{setRemembered(readCheckpointPointer(localStorage.getItem(checkpointStorageKey),projects));}catch{}},[]); + function remember(){try{const value=checkpointPointer(project,issueUrl.trim(),projects);localStorage.setItem(checkpointStorageKey,JSON.stringify(value));setRemembered(value);setStorageNotice('Checkpoint link remembered on this device.');}catch{setStorageNotice('Could not remember this checkpoint. Use a valid project issue URL, or keep the link yourself.');}} + function forget(){try{localStorage.removeItem(checkpointStorageKey);setRemembered(null);setStorageNotice('Checkpoint link removed from this device. The GitHub issue is unchanged.');}catch{setStorageNotice('Browser storage is unavailable.');}} + function restore(){if(!remembered)return;setHandoffNotice('');setProject(remembered.projectId);setIssueUrl(remembered.issueUrl);setMode('checkpoint');setResult(null);setError('');} + const selected=projects.find(p=>p.id===project)!; + const connections=projectConnections(selected); + let checkpointLink='';try{checkpointLink=checkpointPointer(project,issueUrl.trim(),projects).issueUrl;}catch{} + async function run(){setBusy(true);setError('');setCopied(false);setResult(null);try{ + const name=mode==='resume'?'get_project_status':mode==='contribute'?'suggest_contribution':mode==='coordinate'?'prepare_checkpoint':mode==='checkpoint'?'resume_checkpoint':'prepare_feedback'; + const args=mode==='resume'?{project_id:project,refresh:true}:mode==='contribute'?{project_id:project,minutes}:mode==='coordinate'?{project_id:project,...checkpoint}:mode==='checkpoint'?{project_id:project,issue_url:issueUrl.trim()}:{project_id:project,...form}; + const response=await companionRequest({name,args}); + const value=await response.json() as Result;if(!response.ok)throw new Error(value.error||'Could not complete the request.');setResult(value); + }catch(e){setError(e instanceof Error?e.message:'Could not complete the request.');}finally{setBusy(false);}} + function changeMode(value:string){setMode(value);setResult(null);setError('');} + async function exportHandoff(){ + setBusy(true);setError('');setHandoffNotice(''); + try{ + const response=await companionRequest({name:'export_project_handoff',args:{project_id:project}}); + const value=await response.json() as Result; + if(!response.ok)throw Error(value.error||'Export unavailable.'); + const url=URL.createObjectURL(new Blob([JSON.stringify(value,null,2)+'\n'],{type:'application/json'})); + const link=document.createElement('a');link.href=url;link.download='pixie-'+project+'-handoff.json';link.click(); + setHandoffNotice('Download prepared. Check your downloads to keep the JSON file. No shared record was saved or submitted.'); + setTimeout(()=>URL.revokeObjectURL(url),1000); + }catch(e){setError(e instanceof Error?e.message:'Export unavailable.');} + finally{setBusy(false);} + } + async function copy(){try{await navigator.clipboard.writeText(result?.report||'');setCopied(true);}catch{setError('Copy is unavailable. Select the report text below to copy it.');}} + return
Skip to main content +
PIXIE LOPTR LAB
+