Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
223152b
added stealth browser to meet call joins and added session signed out…
juliagsy Jul 27, 2026
c5b1c48
added google meet session refresh on successful login
juliagsy Jul 27, 2026
252915d
updated voice labelling tests to better reflect and test functionality
juliagsy Jul 27, 2026
03711ec
fix(voice): raise segment/realtime duration floors past the CAM++ cli…
juliagsy Jul 27, 2026
3189bd8
fix(voice): withhold verification on ambiguous ids, single-source the…
juliagsy Jul 27, 2026
6ee3709
fix(task_scheduler): compare scheduled instants
YushaArif99 Jul 27, 2026
ec85fac
fixed google meet's browser viewport configuration which might have a…
juliagsy Jul 27, 2026
a87865f
fix(tasks): finalize task runs across contexts
YushaArif99 Jul 27, 2026
42619ab
fix(tasks): clarify nullable trigger capability gates
YushaArif99 Jul 27, 2026
6c54903
style(tasks): format lifecycle fixes
YushaArif99 Jul 27, 2026
aae9167
refactored calls recording logic and storage
juliagsy Jul 27, 2026
a4eb675
made call recording task async and run in parallel
juliagsy Jul 27, 2026
e9f623c
fix(events): persist CodeAct roots
YushaArif99 Jul 27, 2026
563ee1f
Wire shared-agent-context for team transcript search
djl11 Jul 27, 2026
2111912
Add shared_context submodule and update agent archive paths
djl11 Jul 27, 2026
59513cf
Bump shared_context submodule for identity resolution fix
djl11 Jul 27, 2026
21839d5
feat(tasks): render a live task's description in completed actions
YushaArif99 Jul 27, 2026
4435e1b
fix(tasks): pass task description through scheduled and triggered dis…
YushaArif99 Jul 27, 2026
5538aa0
fix(tasks): surface task description for provider-event dispatch
YushaArif99 Jul 27, 2026
165f7e9
Remove shared_context submodule; use adjacent ~/shared_context
djl11 Jul 27, 2026
24bfeb4
Remove shared_context submodule gitlink
djl11 Jul 27, 2026
832a663
Add /phone/recording-url so comms signs call-recording playback
juliagsy Jul 28, 2026
4f46c83
fix(recording): fixed recording transcript timestamps
juliagsy Jul 28, 2026
e593d52
fix(task-scheduler): stamp started_at when a provider-event run starts
YushaArif99 Jul 28, 2026
8e6e0c1
fix(task-scheduler): stop deleting a provider-event task's row twice
YushaArif99 Jul 28, 2026
38ee9de
feat(task-scheduler): support app-slug filtering and pagination when …
YushaArif99 Jul 28, 2026
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
2 changes: 1 addition & 1 deletion .agents/global-rules
4 changes: 2 additions & 2 deletions .env.advanced.example
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ UNITY_ASYNCIO_DEBUG=0

# Orchestra persistence when publishing is on:
# all — write every event to Events/* (default, legacy behavior)
# allowlist — write only ManagerMethod/ToolLoop for listed tools
# allowlist — write only ManagerMethod/ToolLoop for listed action/tool names
# EVENTBUS_ORCHESTRA_PERSIST_MODE=all
# EVENTBUS_ORCHESTRA_PERSIST_TOOLS=execute_code,execute_function
# EVENTBUS_ORCHESTRA_PERSIST_TOOLS=act,execute_code,execute_function

# Stream ManagerMethod/ToolLoop to Pub/Sub for Console Live Actions (independent
# of the Orchestra allowlist; stream_filters apply here only).
Expand Down
14 changes: 7 additions & 7 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@
"filename": "tests/conversation_manager/core/test_event_handlers.py",
"hashed_secret": "00942f4668670f34c5943cf52c7ef3139fe2b8d6",
"is_verified": false,
"line_number": 3555
"line_number": 3874
}
],
"tests/conversation_manager/core/test_event_logging.py": [
Expand Down Expand Up @@ -367,14 +367,14 @@
"filename": "tests/gateway/common/test_livekit.py",
"hashed_secret": "07cc235c65c465c09ab85e3daa9a71f89b3892fd",
"is_verified": false,
"line_number": 38
"line_number": 39
},
{
"type": "Secret Keyword",
"filename": "tests/gateway/common/test_livekit.py",
"hashed_secret": "72cb70dbbafe97e5ea13ad88acd65d08389439b0",
"is_verified": false,
"line_number": 39
"line_number": 40
}
],
"tests/secret_manager/test_dotenv.py": [
Expand Down Expand Up @@ -484,21 +484,21 @@
"filename": "unify/gateway/channels/phone/views.py",
"hashed_secret": "e54919ec32579c37fe783d99d1df750fabeff18f",
"is_verified": false,
"line_number": 103
"line_number": 200
},
{
"type": "Hex High Entropy String",
"filename": "unify/gateway/channels/phone/views.py",
"hashed_secret": "bf0206d3f49e3b278d6ababbc5c93d560a662cb9",
"is_verified": false,
"line_number": 107
"line_number": 204
},
{
"type": "Hex High Entropy String",
"filename": "unify/gateway/channels/phone/views.py",
"hashed_secret": "273a878f5d1b6386edf30435c933904f36bbe713",
"is_verified": false,
"line_number": 111
"line_number": 208
}
],
"unify/gateway/channels/whatsapp/views.py": [
Expand Down Expand Up @@ -534,5 +534,5 @@
}
]
},
"generated_at": "2026-07-24T02:37:51Z"
"generated_at": "2026-07-28T04:20:55Z"
}
682 changes: 387 additions & 295 deletions AGENTS.md

Large diffs are not rendered by default.

59 changes: 57 additions & 2 deletions agent-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,25 @@ const startBrowserOnVm = async (
}

// --- Google Meet browser launcher ---

// Fixed page geometry for the Meet browser.
//
// The width is capped by the vision budget, not by taste: the planner receives
// a screenshot scaled only by 1/devicePixelRatio (magnitude-core's WebHarness)
// and answers in CSS pixels, so the image must reach the model unresized or its
// coordinates land short of their target. ``modelObservationMaxEdge`` in
// ``unify/common/observation_scaling_policy.json`` puts that ceiling at 1568px
// for the Claude models this service runs on, and 1280x800 is one of the
// declared ``defaultAspectTargets`` underneath it — wide enough for Meet's
// desktop layout, small enough to survive the trip intact.
const MEET_VIEWPORT = { width: 1280, height: 800 };

// The OS window has to outsize the page it hosts, or the tab renders into
// something smaller than the viewport it was promised and the screenshot no
// longer matches what a click at those coordinates will hit. The slack covers
// the tab strip and omnibox.
const MEET_WINDOW_HEIGHT = MEET_VIEWPORT.height + 120;

const startGoogleMeetBrowser = async (
meetUrl: string,
storageStateName?: string,
Expand All @@ -862,6 +881,8 @@ const startGoogleMeetBrowser = async (
"--disable-features=IsolateOrigins,site-per-process",
'--auto-select-desktop-capture-source="Entire screen"',
'--auto-select-tab-capture-source-by-title=Desktop',
`--window-size=${MEET_VIEWPORT.width},${MEET_WINDOW_HEIGHT}`,
"--window-position=0,0",
],
env: {
...process.env,
Expand All @@ -872,10 +893,23 @@ const startGoogleMeetBrowser = async (
tracesDir: defaultBrowserPaths.tracesDir || undefined,
},
contextOptions: {
viewport: null,
// Pinned rather than inherited from the window (``viewport: null``):
// every click the planner emits is an absolute coordinate read off a
// screenshot, so the join is only as reliable as the layout is
// reproducible. An inherited viewport is whatever Chromium negotiates
// with the window manager — a 1920x1080 desktop yielded a 937px-wide
// page, which is Meet's narrow layout and a different control map from
// the one the same prompts hit elsewhere. See MEET_VIEWPORT for the
// width choice.
viewport: { ...MEET_VIEWPORT },
ignoreHTTPSErrors: true,
permissions: ['camera', 'microphone'],
},
// Anti-automation hardening (magnitude-core web/stealth.ts). Google flags
// an un-hardened automated Chromium and forces the persistent session
// into a reauth challenge, degrading the signed-in join to an anonymous
// guest that Meet turns away. Keep it on for the Meet browser.
stealth: true,
};
if (storageStateName) {
browser.storageStateName = storageStateName;
Expand Down Expand Up @@ -1143,6 +1177,23 @@ async function googleMeetJoinFlow(agent: BrowserAgent): Promise<GoogleMeetJoinRe
const pageUrl = page.url?.() ?? 'unknown';
console.log(`[googlemeet/join] Page loaded: url=${pageUrl}`);

// Signed-out / reauth guard. When the persistent storage state has expired,
// Google redirects to accounts.google.com (password reauth, "Verify it's
// you") instead of the Meet green room. Proceeding from here would silently
// degrade to an anonymous guest that Meet turns away, so detect it and fail
// fast with a distinct status the caller can act on (session renewal) rather
// than burning iterations and reporting a misleading lobby/blocked outcome.
const onAccountsDomain = /accounts\.google\.com/i.test(pageUrl);
const reauthSelector = "text=/verify it.?s you|enter your password|wrong password|couldn.?t sign you in/i";
const reauthVisible = await page.locator(reauthSelector).first().isVisible({ timeout: 1500 }).catch(() => false);
if (onAccountsDomain || reauthVisible) {
const reauthMsg = reauthVisible
? await page.locator(reauthSelector).first().textContent().catch(() => 'reauth')
: 'accounts.google.com';
console.warn(`[googlemeet/join] signed-out/reauth detected: "${reauthMsg}" (url=${pageUrl})`);
return { status: 'error', reason: `meet_signed_out: "${reauthMsg}" (url=${pageUrl})` };
}

// Phase 1: pre-join prep (popups, camera off). The browser joins signed in
// via persistent storage state, so there is no name field to fill.
console.log('[googlemeet/join] Phase 1: prepare...');
Expand Down Expand Up @@ -1353,7 +1404,11 @@ app.post('/browser-states/:name/save', async (req: Request, res: Response) => {
message: 'A valid browser state name and sessionId are required',
});
}
const session = activeSessions.get(sessionId);
// Google Meet joins live in their own session store, and Meet is the only
// flow that runs a signed-in browser, so its sessions must be reachable here
// for cookie write-back after a join. Teams joins are anonymous and have no
// state to persist.
const session = activeSessions.get(sessionId) ?? googleMeetSessions.get(sessionId);
if (!session) {
return res.status(404).json({ error: 'session_not_found', message: 'Browser session does not exist' });
}
Expand Down
6 changes: 3 additions & 3 deletions sandboxes/conversation_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ To keep interactive EventBus traffic sparse in Orchestra while still retaining a
```bash
export EVENTBUS_PUBLISHING_ENABLED=true
export EVENTBUS_ORCHESTRA_PERSIST_MODE=allowlist
export EVENTBUS_ORCHESTRA_PERSIST_TOOLS=execute_code,execute_function
export EVENTBUS_ORCHESTRA_PERSIST_TOOLS=act,execute_code,execute_function
# optional Live Actions stream (unchanged by the allowlist):
# export EVENTBUS_PUBSUB_STREAMING=true
```

In ``allowlist`` mode:

- **Outside** a task run: only allowlisted tools (default ``execute_code`` /
``execute_function``) are written to ``Events/*``.
- **Outside** a task run: only allowlisted action/tool names (default ``act``,
``execute_code``, and ``execute_function``) are written to ``Events/*``.
- **Inside** an ``ActiveTask`` (``CURRENT_TASK_RUN_LINEAGE`` / payload
``run_key`` + ``task_id``/``instance_id``): the full ManagerMethod + ToolLoop
tree is persisted and stamped for join from ``Tasks/Executions``.
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@ def pytest_unconfigure(config):
os.environ["HOME"] = _original_home
if _hf_home_set_by_us:
os.environ.pop("HF_HOME", None)
if _speaker_model_set_by_us:
os.environ.pop("UNIFY_SPEAKER_MODEL_PATH", None)


def pytest_terminal_summary(terminalreporter, exitstatus, config):
Expand All @@ -661,6 +663,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):

_original_home: str | None = None
_hf_home_set_by_us: bool = False
_speaker_model_set_by_us: bool = False


def pytest_configure(config):
Expand Down Expand Up @@ -703,6 +706,22 @@ def pytest_configure(config):
os.environ["HF_HOME"] = original_hf
_hf_home_set_by_us = True

# Same problem, same fix, for the speaker-embedding model: it is cached
# under ~/.cache/unify/speaker_id/ and the HOME override hides it, so
# every real-model speaker-identification test silently skips instead of
# running. Globbing (rather than importing speaker_id for the filename)
# keeps pytest_configure free of heavy imports.
global _speaker_model_set_by_us
if "UNIFY_SPEAKER_MODEL_PATH" not in os.environ and _original_home:
import glob as _glob

cached_models = _glob.glob(
os.path.join(_original_home, ".cache", "unify", "speaker_id", "*.onnx"),
)
if len(cached_models) == 1:
os.environ["UNIFY_SPEAKER_MODEL_PATH"] = cached_models[0]
_speaker_model_set_by_us = True

config.addinivalue_line(
"markers",
"requires_real_unify: mark test as requiring the real unify implementation",
Expand Down
Loading
Loading