Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions extensions/assistive_harness/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
# Assistive Voice Skill Harness (Phase A / Phase B Core)
# Assistive Voice Skill Harness (Phase A)

This directory is the transport-neutral control Core shared by the optional
MiniCPM-o browser sidecar and OpenGlass Phase B device adapters. It is disabled
by default and does not replace the native microphone/video path.

## Clean-clone setup

From the OpenGlass repository root, install the Core and explicitly download
the public ASR model:

```powershell
python -m pip install -r extensions/assistive_harness/requirements.txt
python -m extensions.assistive_harness.download_modelscope_model
```

The second command prints the downloaded local directory and a complete start
command. Model weights are not stored in this Git repository. The runtime never
downloads a model implicitly: `--model-path` must point to an existing local
directory, otherwise startup fails clearly.
This directory is an optional, local sidecar for the MiniCPM-o browser Demo. It is
disabled by default and does not replace the native microphone/video path.

Start the sidecar explicitly:

Expand All @@ -26,10 +10,7 @@ python -m extensions.assistive_harness.server --enabled `
--model-path "C:\path\to\a\local\FunASR\model"
```

Then opt the browser tab in with `?assistive_harness=1`. Browser integration
assets and their hook contract live in
[`../../integrations/minicpm_browser/`](../../integrations/minicpm_browser/).
Test-only transcript
Then opt the browser tab in with `?assistive_harness=1`. Test-only transcript
injection additionally requires `--allow-test-injection`; it is never enabled by
the normal command above.

Expand Down
1 change: 1 addition & 0 deletions extensions/assistive_harness/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
"RuleIntentRouter",
"SkillRegistry",
]

1 change: 1 addition & 0 deletions extensions/assistive_harness/asr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from .funasr_engine import FunASREngine

__all__ = ["ASREngine", "ASRResult", "EnergyVAD", "FunASREngine", "UtteranceAudio"]

1 change: 1 addition & 0 deletions extensions/assistive_harness/asr/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ def transcribe(self, audio: np.ndarray, sample_rate: int) -> ASRResult:
del audio, sample_rate
text = self.transcripts.pop(0) if self.transcripts else ""
return ASRResult(text=text, confidence=1.0, model="scripted", device="cpu")

1 change: 1 addition & 0 deletions extensions/assistive_harness/asr/energy_vad.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,4 @@ def reset(self) -> None:
self._active = []
self._started_at_ms = None
self._last_voice_ms = None

6 changes: 3 additions & 3 deletions extensions/assistive_harness/config/skills.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ cv:
type: noop
yolo_onnx:
type: yolo_onnx
# Resolved relative to this YAML. Weights stay in OpenGlass/models and
# are ignored by Git; the Core never downloads them implicitly.
model_path: ../../../models/yolo26n.onnx
# Resolved relative to this YAML. Keep weights outside the Core so the
# same plugin can be reused by the later OpenGlass Adapter.
model_path: ../../../../OmniHarness/mini_omni_harness/models/yolo26n.onnx
device: cpu
confidence: 0.25
image_size: 640
Expand Down
4 changes: 2 additions & 2 deletions extensions/assistive_harness/cv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ skills:
```

Relative model paths are resolved against the YAML directory. Weights are not
owned by the Core. The shipped sample resolves to the Git-ignored local file
`OpenGlass/models/yolo26n.onnx`.
owned by the Core. The current local sample points to the previously validated
`OmniHarness/mini_omni_harness/models/yolo26n.onnx` file.

## Provider contract

Expand Down
69 changes: 69 additions & 0 deletions extensions/assistive_harness/package_phase_a.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path,
[string]$Date = '2026-08-11'
)

$ErrorActionPreference = 'Stop'
$deliverables = Join-Path $RepoRoot 'deliverables'
$packageName = "VOICE_SKILL_HARNESS_PHASE_A_CODEX_RETURN_$Date"
$stage = Join-Path $deliverables $packageName
$zipPath = Join-Path $deliverables "$packageName.zip"

$resolvedRepo = (Resolve-Path $RepoRoot).Path.TrimEnd('\')
New-Item -ItemType Directory -Force -Path $deliverables | Out-Null
$resolvedDeliverables = (Resolve-Path $deliverables).Path
if (-not $resolvedDeliverables.StartsWith($resolvedRepo, [StringComparison]::OrdinalIgnoreCase)) {
throw "Unsafe delivery target: $resolvedDeliverables"
}
if (Test-Path $stage) { Remove-Item -LiteralPath $stage -Recurse -Force }
if (Test-Path $zipPath) { Remove-Item -LiteralPath $zipPath -Force }
New-Item -ItemType Directory -Force -Path $stage | Out-Null

function Copy-TreeFiltered([string]$Source, [string]$Destination) {
Get-ChildItem -LiteralPath $Source -Recurse -File | Where-Object {
$_.FullName -notmatch '[\\/]runs[\\/]' -and
$_.FullName -notmatch '[\\/]__pycache__[\\/]' -and
$_.FullName -notmatch '[\\/]test_artifacts[\\/]' -and
$_.Extension -ne '.pyc'
} | ForEach-Object {
$relative = $_.FullName.Substring($Source.TrimEnd('\').Length).TrimStart('\')
$target = Join-Path $Destination $relative
New-Item -ItemType Directory -Force -Path (Split-Path $target) | Out-Null
Copy-Item -LiteralPath $_.FullName -Destination $target
}
}

Copy-TreeFiltered (Join-Path $RepoRoot 'extensions\assistive_harness') (Join-Path $stage 'extensions\assistive_harness')
Copy-TreeFiltered (Join-Path $RepoRoot 'static\assistive_harness') (Join-Path $stage 'static\assistive_harness')
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'static\omni') | Out-Null
Copy-Item -LiteralPath (Join-Path $RepoRoot 'static\omni\omni-app.js') -Destination (Join-Path $stage 'static\omni\omni-app.js')

$docNames = @(
'VOICE_SKILL_HARNESS_AUDIT.md',
'VOICE_SKILL_HARNESS_DESIGN.md',
'VOICE_SKILL_HARNESS_EXECUTION_REPORT.md',
'VOICE_SKILL_HARNESS_GO_NO_GO.md',
'VOICE_SKILL_HARNESS_RERUN_COMMANDS.md',
'VOICE_SKILL_HARNESS_CHANGED_FILES.md'
)
$docTarget = Join-Path $stage '_codex_context'
New-Item -ItemType Directory -Force -Path $docTarget | Out-Null
foreach ($name in $docNames) {
$text = Get-Content -Raw -Encoding UTF8 (Join-Path $RepoRoot "_codex_context\$name")
$text = $text.Replace($env:USERPROFILE, '%USERPROFILE%')
Set-Content -Encoding UTF8 -NoNewline -Path (Join-Path $docTarget $name) -Value $text
}

$manifest = [ordered]@{
project = 'Voice-Controlled Skill Harness'
phase = 'A_MINICPMO_BROWSER'
date = $Date
ready_for_phase_b = $false
feature_default = 'off'
excluded = @('model weights', 'raw audio/video', 'runs', 'credentials', 'absolute user paths')
}
$manifest | ConvertTo-Json -Depth 5 | Set-Content -Encoding UTF8 (Join-Path $stage 'MANIFEST.json')

Compress-Archive -Path (Join-Path $stage '*') -DestinationPath $zipPath -CompressionLevel Optimal
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $zipPath).Hash
[pscustomobject]@{Zip=$zipPath; SHA256=$hash}
Loading