Conversation
…ared contract tests Adds 4 new tests in test_offline_runner_contract.py that prove field-for-field that Communication's NEW _build_offline_runner_env composition (shared Unity contract + hosted-only assistant-identity layer) produces dicts identical to the OLD monolithic Communication builder, across the scheduled, triggered, entrypoint-override, and sparse-assistant-data scenarios. The golden reference function is a verbatim copy of Communication's pre-refactor builder inlined into the test file. If anything in the shared contract drifts from the old behaviour, these tests fail loudly here, in Unity's test suite, before reaching Communication's deployment. Brings total contract-module test count to 35 (up from 31).
…Captcha Exposes a deterministic, Python-callable primitive `WebSessionHandle.solve_captcha()` on every web session created via `cp.web.new_session(...)`. The primitive delegates the visible reCAPTCHA v2 challenge to the AntiCaptcha worker pool and injects the returned Google-signed token back into the live page so the page's own submit flow accepts the verification. Layers wired: - agent-service: new `POST /captcha/solve` handler (sitekey extraction + createTask/getTaskResult polling + page.evaluate injection). Reads `ANTICAPTCHA_KEY` only from `process.env`; token is never logged or echoed in the response. - Python: `ComputerSession.solve_captcha` (+ matching mock-backend and `_MockSession` stubs) with rich docstring on `_LowLevelActionsMixin`. `ComputerSession._request` gains a keyword- only `timeout` parameter (default preserves existing behaviour). - Runtime exposure: `"solve_captcha"` appended to `_COMPUTER_METHODS` and `ComputerPrimitives._LOW_LEVEL_METHODS`; excluded from `_DESKTOP_METHODS` (desktop sessions have no DOM target). - Config: optional `ANTICAPTCHA_KEY` documented in `agent-service/README.md`; missing key surfaces as 503 `anticaptcha_key_missing`. - Tests: mock-backend coverage in `test_computer_multimode.py` guarding the auto-wiring and the default/invisible variant paths. Magnitude-core is intentionally untouched: the primitive is not in the LLM action vocabulary. Callers reach for it from their own orchestration code after a prior `observe()` has confirmed a CAPTCHA is on screen. Out of scope: v3/Enterprise reCAPTCHA, hCaptcha, Turnstile, FunCaptcha, GeeTest, desktop-mode equivalents, and wiring into specific actor/extractor flows.
Clean up the open-source-ready repo surface: - .gitignore now covers build/, dist/, *.egg-info/ (any name), and Local/ so setuptools/uv build output and personal workspace dirs stay out of git status. Deleted ~12MB of build/, dist/, unity.egg-info/, unify_agent.egg-info/, Local/, __pycache__/, .cache.ndjson from disk. - AGENTS.md distilled from .cursor/rules/ so Claude Code, Codex, Aider, Cline, and other assistants pick up the same conventions Cursor does (testing philosophy, no-defensive-coding, explicit-path commits, state-manager design rules, repo map). No code changes.
…anner Brings .github/ in line with peer open-source AI-assistant repos (NousResearch/hermes-agent, openclaw/openclaw) so contributors land on a familiar surface and supply-chain hygiene is visible. Added: - CODEOWNERS — @unifyai/engineers as catch-all + explicit ownership of security-sensitive paths (CODEOWNERS itself, dependabot.yml, workflows/, SECURITY.md, AGENTS.md, ARCHITECTURE.md, secret_manager/). - PULL_REQUEST_TEMPLATE.md — Summary / type / areas / test plan / migration / checklist. References the .cursor/rules invariants (no-defensive-coding, no-temporal-comments, zero-backcompat target). - ISSUE_TEMPLATE/{config,bug_report,feature_request}.yml — bug template routes by surface (CLI / voice / installer / specific manager / ConversationManager / etc.) and asks for `unity doctor` output; feature template explicitly steers users toward GuidanceManager/FunctionManager for runtime-extension requests so the issue queue isn't drowned in "please add this skill" tickets. - dependabot.yml — github-actions weekly (grouped minor/patch) + agent-service npm weekly. Deliberately skips scheduled pip updates per the editable-sibling install model (unify/unillm/orchestra-core); CVE-driven pip security updates remain enabled at the repo-settings level. Comment explains the rationale. - workflows/osv-scanner.yml — Google's reusable workflow pinned by SHA. Scans uv.lock + agent-service/package-lock.json on lockfile changes, push to main/staging, and weekly. SARIF results land in the Security tab; fail-on-vuln disabled so pre-existing CVEs don't block merges.
Lockfile bumps only — no pyproject.toml / package.json changes. Triggered by the 15 open Dependabot alerts on the default branch (see https://github.com/unifyai/unity/security/dependabot). uv.lock (7 bumps): - urllib3 2.6.3 -> 2.7.0 CVE-2026-44431 (high) cross-origin header leak in proxied redirects - urllib3 2.6.3 -> 2.7.0 CVE-2026-44432 (high) decompression-bomb bypass in streaming API - langchain-core 1.3.0 -> 1.4.0 CVE-2026-44843 (high) unsafe deserialization via overly broad load() allowlists (pulls in new transitive langchain-protocol 0.0.15) - python-multipart 0.0.26 -> 0.0.29 CVE-2026-42561 (high) DoS via unbounded multipart part headers - lxml 6.0.3 -> 6.1.1 CVE-2026-41066 (high) XXE in default config of iterparse() and ETCompatXMLParser() - langsmith 0.7.33 -> 0.8.5 CVE-2026-45134 (high) public prompt pull deserializes untrusted manifests - authlib 1.7.0 -> 1.7.2 CVE-2026-44681 (medium) OIDC implicit/hybrid open redirect (not reachable — we don't run an OIDC provider — but bumped for hygiene) - idna 3.11 -> 3.16 CVE-2026-45409 (medium) IDNA encode() bypass of CVE-2024-3651 fix agent-service/package-lock.json (2 bumps, via npm audit fix): - qs 6.15.0 -> 6.15.2 CVE-2026-8723 (medium) qs.stringify DoS on null/undefined entries in comma-format arrays - ws 8.18.3 -> 8.21.0 CVE-2026-45736 (medium) uninitialized memory disclosure Not addressed in this commit (blocked on sibling repos): - litellm 1.83.4 -> 1.83.10 (clears 4 alerts: 1 critical SQLi in proxy, 3 high — sandbox escape, RCE via MCP stdio, SSTI in /prompts/test). All four CVEs are in the LiteLLM *proxy server* surface, which Unity does not run; reachability is effectively zero, but the bump should land for defense in depth. BLOCKED: unillm pins litellm==1.83.4 exactly. The unillm Dependabot PR is already open at unifyai/unillm#54. - python-dotenv 1.0.1 -> 1.2.2 (CVE-2026-28684, medium — symlink-following in set_key; Unity only reads .env so not reachable). BLOCKED: litellm 1.83.4 ships an unusual pin (python-dotenv>=1.0.1,<1.0.1+) that effectively freezes python-dotenv at 1.0.1. Will unblock once unillm#54 lands and `uv sync` brings litellm 1.83.10 in.
The agent-service /captcha/solve handler (added in c9ba909) reads process.env.ANTICAPTCHA_KEY at request time and returns 503 anticaptcha_key_missing if it's unset. Document the env var alongside the other optional integration keys so operators know where to put it without having to read the agent-service README. The actual key value lives in GCP Secret Manager under projects/responsive-city-458413-a2/secrets/ANTICAPTCHA_KEY, alongside the other runtime API keys (ANTHROPIC_API_KEY, DEEPGRAM_API_KEY, LIVEKIT_API_KEY, etc.). The companion unity-deploy commit adds ANTICAPTCHA_KEY to setup_k8s_config.py's required_secrets list so the unity-secrets K8s Secret picks it up automatically on cluster setup.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
The script's `discover_all()` was only recursing into top-level tests/
sub-directories whose names start with `test` — but Unity's convention
is to name per-manager test directories after the manager itself
(contact_manager/, knowledge_manager/, actor/, task_scheduler/,
conversation_manager/, etc.) without the `test_` prefix.
Effect: the staging→main CI matrix was silently collapsing to just 2
entries (tests/test_integration_status/ and tests/test_session_details.py
— the only top-level paths starting with `test`) instead of the ~67
leaf paths that actually exist. Every prior release went green on a
hollow signal exercising none of the manager test suites.
Fix: replace `item.name.startswith("test")` with
`item.name not in EXCLUDE_DIRS`. Safe because `collect_paths()` is
itself gated by `has_test_files`/`has_test_subdirs`, so recursing into
a non-test directory is a no-op. EXCLUDE_DIRS already covers
__pycache__, .pytest_cache, .venv, etc.
Verified locally: `python3 .github/scripts/discover_test_paths.py | wc -l`
returns 67 (was 2), and the output now includes tests/contact_manager,
tests/task_scheduler, tests/actor/*, tests/conversation_manager/*, etc.
… walk, cwd, HOME, pid) The earlier diagnostic (6c04f36) confirmed venv_dir.exists()= False between prepare_venv() return and create_subprocess_exec() on Linux CI. Local repro on macOS doesn't reproduce the disappearance, and no codepath in unity/, unify/, or unillm/ rmtrees the .unity/venvs/ tree. To narrow which tree level is being wiped, expand the diagnostic to dump: - PID + cwd + HOME at the failure point (in case some sibling test changed cwd / HOME after the prepare_venv chdir) - Existence of every Path ancestor from python_path up to "/" (deepest first), so we can tell whether the wipe is leaf-only (just the venv_id dir) or full-tree (.unity/venvs/) - Grandparent directory listing (the safe_ctx-keyed dir holding all venv_id subdirs for this test context); if it exists with OTHER venv-id subdirs, suspect per-venv-id cleanup; if missing, suspect higher-level rmtree The structured error message lets the next matrix run reveal the actual rmtree scope without further code changes, narrowing the search to the right code path. No behavior change otherwise — the structured RuntimeError is still raised in place of the generic FileNotFoundError.
…introduced third-parties
Two related LLM-eval failures on the conv_mgr/voice fast-brain cluster:
1. test_redundant_checking_guidance_avoids_same_deferral_phrase:
Scenario: assistant has already said "Let me check on that."
then a `[notification]` confirms it's checking. On the next
turn, the LLM said "Let me check on that." AGAIN — exact
verbatim repeat. The existing "Do NOT over-acknowledge or
send multiple confirmations" line in Communication
guidelines is too generic; the LLM took it to mean "don't
send multiple replies", not "don't reuse the same phrase".
Fix: add an explicit bullet right under the existing one:
"Never repeat the same deferral / filler phrase verbatim
across consecutive turns" — with concrete varied
alternatives ("Still looking…", "Almost there", "One moment
more", or stay silent via `wait`). Naming the verbatim-
repeat case directly avoids the LLM rationalising the
repeat as a fresh ack.
2. test_demo_introduction_without_name_in_greeting:
Scenario: boss says "I'm here with Maria — Maria, go ahead
and ask Alex anything", then Maria (as the next user
message) asks "can you pronounce my name?" The LLM replied
"Sure — how do you spell it?" — completely failing to
carry "Maria" forward from the introduction.
The voice prompt had no guidance about WHO is currently
speaking when multiple parties share the line. The model
sees both messages as role=user and didn't infer the
speaker switch.
Fix: add a "Tracking who's currently speaking" sub-section
to the voice prompt's pacing block. Explicitly states that
after a "here with X" / "I'll hand you over to Y"
introduction, the next turn IS X/Y, and self-referential
language ("my name", "I'm thinking…") refers to the
introduced person. Names the exact failure case as the
wrong-answer example: asking "how do you spell it?" after
"this is Maria" sounds inattentive.
Both are prompt-engineering nudges; no code path changes.
Caches will invalidate naturally since both nudges change the
system prompt text.
…nterjected questions test_interjection (in tests/transcript_manager/test_ask.py) was failing: 1) tm.ask(Q1) # "When did Dan last speak with Julia on the phone?" 2) handle.interject(Q2) # "Did Jimmy ever tell us when he's on holiday?" 3) handle.result() # expects answers to BOTH Q1 and Q2 The LLM-judge assertion (_llm_assert_correct with multiple_answers=True) checks the final reply contains both the Dan-Julia date AND the Jimmy holiday date. Production LLM behavior was to abandon Q1 entirely once Q2 came in and reply with just Jimmy's date — natural conversational behavior but not what the test (or a power-user querying transcripts) expects. The TM ask prompt previously had no guidance about how to handle interjections. Add a global directive: "treat interjections as ADDITIVE — final reply must cover BOTH the original question AND the interjected one, not just the latest." This makes the test's expectation explicit in the prompt rather than relying on the LLM to infer the right behavior. This is the right semantic for a transcript-query loop specifically — when someone interjects an extra question while you're querying transcripts, they typically want both questions answered, not the first abandoned. The same nudge wouldn't apply uniformly to all loops (e.g. ConversationManager actions where interjection often means "course correct the current action", not "do both") so it lives only in the TM ask prompt.
… when caller asks "what is this about?"
test_triggered_wake_explains_topic_naturally was failing:
System prompt: voice agent (Alex calling Alice)
Conversation:
1) assistant [notification]: "this phone call from Alice
may relate to the task 'Invoice follow-up'. ... Do not
mention the task unless it naturally helps."
2) user (Alice): "Sure, what is this about?"
Expected response: mentions "invoice", "follow-up", or "alice"
Actual response: "Hi, how can I help?"
The LLM was being overly conservative about the "do not
mention unless it naturally helps" hedge in the wake
notification. "What is this about?" is the canonical scenario
where mentioning the topic naturally helps — but the fast
brain was treating the hedge as "stay silent about it".
The existing "Notification authority" block only covered
completion notifications. It said nothing about wake-context
(why the assistant is awake / why a call is happening). Add a
new sub-section "Wake-context notifications" that:
- Explicitly identifies the pattern: "Background context:
this call may relate to X" / "<task X> is due now"
- Names "what's this about?" / "why did you call?" as the
canonical "should mention the topic" trigger
- Reinterprets the hedge phrasing ("may relate", "still
deciding", "do not mention unless it naturally helps") as
"lead with the topic but stay open to redirection", NOT as
"stay silent"
- Gives the exact failing example as the wrong-answer
("Hi, how can I help?" — ignores the context I was given)
- Gives concrete right-answer phrasings ("Wanted to follow
up on the invoice — is now a good time?")
- Reiterates: never quote internal phrasing aloud (already
enforced elsewhere, but adjacent to this block for clarity)
Prompt-engineering nudge only; no code paths changed.
…stant to skip CI-broken wake-up
The per-test ephemeral-assistant fixture (added earlier in this
session) was failing in CI with 500 Internal Server Error on
every POST /v0/assistant call. Orchestra-side stack trace shows:
views.py:788 create_assistant
-> assistant_infra.py:1652 wake_up_assistant
-> httpcore.UnsupportedProtocol: Request URL is missing an
'http://' or 'https://' protocol.
`wake_up_assistant` builds
`_adapters_url_for(deploy_env) + "/assistant/wakeup"`. In CI
there is no Communication / adapters service running, so
`_adapters_url_for("")` returns "" and httpx rightly refuses
to POST to a protocol-less URL.
The orchestra view (Phase 3 in create_assistant) only calls
wake-up when `not assistant_in.is_local`. The intent of
`is_local=True` is "unity runs locally, no remote infra
needed" — exactly what every CI test wants. The fixture
already passes `create_infra=False` (skips pubsub/VM
provisioning); adding `is_local=True` skips the adapters
wakeup webhook too, completing the "no external services
required" picture.
Long-term, orchestra's `wake_up_assistant` should fail
gracefully (or no-op) when `_adapters_url_for` returns empty,
since a 500 here masks the real configuration issue.
Cross-repo fix can land separately; the test-side flag is the
correct caller behavior anyway (CI assistants are local
fixtures by definition).
…s.agent_id Two unrelated function_manager/python failures from the CI matrix that were blocking the whole cluster: 1. `uv sync` failing with "Distribution not found at: file:///tmp/.../<venv_dir>" on every test that creates a venv: The synthetic pyproject.toml generated for each user venv only declares `[project]` + `dependencies`. It is NOT an installable package. Without `--no-install-project`, uv tries to install the project itself (in editable mode), fails to find a build backend / sdist for the empty venv_dir, and surfaces "Distribution not found" — which then masquerades as a venv-creation failure. Adding `--no-install-project` to the `uv sync` invocation tells uv "install dependencies into .venv, do NOT install the project itself". The project manifest is just a dependency declaration for us, never a real package. Verified the flag exists in current uv. The CI failure was reliably reproducible across every test in the `test_venv_*` family. 2. `AttributeError: AssistantDetails ... has no attribute 'id'` in test_remote_windows fixtures: `mock_session_details_windows` and `mock_session_details_ubuntu` were calling `monkeypatch.setattr(SESSION_DETAILS.assistant, "id", "test-assistant")`. Same legacy `.id` → `.agent_id` rename that bit contact_manager tests earlier. AssistantDetails is a Pydantic model with the new field name `agent_id` (int); setting an arbitrary attribute fails because the model rejects unknown fields. Switched the fixtures to set `agent_id` to a unique int per fixture (999_001 / 999_002) to keep the two windows / ubuntu variants distinguishable in any downstream test assertions.
…gger The earlier fix (f9d2289) called caplog.set_level(INFO, logger="unity"), on the assumption that this would also subscribe caplog to the named logger. That assumption was wrong: - pytest's caplog.handler is registered with the ROOT logger only, via the `propagate=True` chain. - `caplog.set_level(level, logger=name)` sets the LEVEL on the named logger but does NOT attach caplog.handler to it. - unity/logger.py sets `LOGGER.propagate = False` (since 5ed695f, 2026-02-20 "Consolidate logging into unity.logger as single authority"), so unity log records never reach the root handler. Net effect: caplog.records stays empty for unity LOGGER output even after set_level(..., logger="unity"). Verified locally with a minimal test (`logging.getLogger("unity")` + `LOGGER.info(...)`) — without the explicit `addHandler(caplog.handler)`, the captured records list is empty; with the explicit add, the message appears immediately. Fix: explicitly `logging.getLogger("unity").addHandler(caplog.handler)` at the top of the test body, wrap the assertion block in try/finally, and `removeHandler(caplog.handler)` in finally so the handler doesn't leak across tests (each test gets a fresh caplog with a fresh handler, and a stale handler would write to a disposed buffer). The existing `set_level(..., logger="unity")` call is left as-is so the unity logger's effective level still includes INFO during the test (otherwise INFO records would be filtered before reaching any handler).
…NGS picks them up
The env vars in `pytest_configure(config)` (USER_DESKTOP_CONTROL_ENABLED,
ASSISTANT_EMAIL, ASSISTANT_NUMBER, ASSISTANT_WHATSAPP_NUMBER) were
landing too late.
Order of operations:
1. pytest collects this conftest.py → runs `from tests.helpers import ...`
at module top
2. tests/helpers.py transitively imports unity modules
3. unity.settings instantiates `SETTINGS = ProductionSettings()` —
pydantic-settings reads env vars **once**, at this point.
4. pytest_configure() runs — by now SETTINGS is frozen, so the env
overrides are silently ignored.
Symptom (caught in conv_mgr/flows + conv_mgr/voice + conv_mgr/core CI):
- test_can_you_use_my_computer: LLM answered "Not directly" instead
of "Yes — install a quick remote-access tool from unify.ai",
because SETTINGS.conversation.USER_DESKTOP_CONTROL_ENABLED was
False at SETTINGS-instantiation time → the desktop_access_faq
prompt branched the wrong way.
- test_reply_adds_re_prefix_to_subject: LLM didn't emit EmailSent
because `send_email` wasn't surfaced as a tool (gated on non-
empty assistant.email; SESSION_DETAILS.assistant.email was still
"" because ASSISTANT_EMAIL env var hadn't been read at populate
time).
Fix: hoist the env-var setdefaults to the very top of conftest.py,
BEFORE the `from tests.helpers import ...` line. Add a header comment
documenting the timing requirement so future hands don't move them
back into pytest_configure() "for tidiness". The redundant copies in
pytest_configure() stay as a defense-in-depth (in case a downstream
test reimports SETTINGS) but the authoritative point of truth is
now the module top.
…exist, restore _sync_required_contacts In commit 2b07266 I renamed the SyncContacts handler call from contact_manager._sync_required_contacts → _provision_system_overlays in both event_handlers.py and the matching test. The rename was wrong: _provision_system_overlays is NOT defined anywhere on ContactManager / BaseContactManager / SimulatedContactManager — only _sync_required_contacts is. Symptoms: - Production: every SyncContacts event raised AttributeError on cm.contact_manager._provision_system_overlays (caught and logged by the try/except in event_handlers.py:_sync_contacts, silently dropping the sync — no boom in logs, just no-op). - Tests: test_queue_operation_waits_for_initialization patched the same wrong name; production handler now AttributeError'd inside the try/except, never reached the (non-existent) overlay call, mock_sync.called stayed False, assert_called_once() failed with "Expected '_sync_required_contacts' to have been called once" (mock display name inherited from the wraps= target, which itself failed to resolve). Fix is purely the revert. Restore the actual production method name in BOTH the handler and the test. Verified locally (`pytest test_queue_operation_waits_for_initialization -xvs` → 1 passed). Lesson for future renames: confirm the target method exists on every concrete implementation (BaseContactManager, ContactManager, SimulatedContactManager) before committing the rename — `git grep "def _provision_system_overlays"` would have shown zero matches and flagged this immediately.
…n't depend on cwd test_screenshot_crop_via_act was failing with: FileNotFoundError: [Errno 2] No such file or directory: 'Screenshots' `generate_screenshot_path(entry)` returns a relative path (`Screenshots/User/<ts>.jpg`). Production code calls it from inside a worker that runs in `cwd=local_root` — see `conversation_manager/main.py:os.chdir(_local_root)` early in startup. Test fixtures spin up CM in-process via CMStepDriver without that chdir, so when the test calls `write_screenshot_to_disk` directly, the relative path resolves against whatever cwd pytest is in (usually the repo root), and `p.parent.mkdir(parents=True)` raises because `./Screenshots/` isn't there. Earlier in the test we already mkdir'd `local_root / "Screenshots" / "User"` (line 61) and the test later globs `local_root / "Screenshots" / "User"` for the written file (line 108), so the test already KNOWS where the screenshot should live — it just wasn't telling write_screenshot_to_disk explicitly. Fix: absolutise the path before the disk write: screenshot_path = str(local_root / generate_screenshot_path(entry)) This keeps the production codepath (relative + cwd chdir) untouched and makes the test independent of cwd. No prod change.
…on exit (parallel race)
ROOT CAUSE found for the function_manager/python "venv python
disappeared between prepare_venv() and create_subprocess_exec()"
RuntimeError that has been failing reliably on every CI matrix
run for weeks.
The pytest_unconfigure hook was calling
`shutil.rmtree("/tmp/unity_test_home")` at the end of EVERY pytest
session. parallel_run.sh launches one pytest session per test in
parallel tmux panes, all of them sharing the same deterministic HOME
(by design — LLM cache keys embed ~/Unity/Local and must stay
stable). When session A finishes, its pytest_unconfigure wipes the
entire tree — including the venvs that session B/C/D's
FunctionManager just created under
`$HOME/Unity/Local/.unity/venvs/<ctx>/<id>/`. The next
`execute_in_venv` in B/C/D then sees:
venv_dir=/tmp/unity_test_home/Unity/Local/.unity/venvs/<ctx>/0
exists=False
ancestor existence: False all the way up to /tmp/
The earlier diagnostic dump (commit 1e9a5f4) confirmed the wipe
scope was full-tree, not leaf-only, ruling out per-venv-id cleanup.
Fix: drop the rmtree. The shared HOME stays, but:
- CI runners are ephemeral — the dir is reclaimed when the
runner shuts down.
- Local dev: users can `rm -rf /tmp/unity_test_home` manually.
- Test isolation is already enforced by per-test Unify contexts
(each FunctionManager venv lives under a context-keyed
subdirectory), so leftover files from one test do not affect
another.
This single-line change unblocks every test in
function_manager/python and likely fixes a long tail of
intermittent "vanishing fixture file" failures elsewhere too.
… doesn't time out
Five test_remote_windows tests were failing in CI with:
RuntimeError: Managed VM did not become ready within 5 minutes
The production helper
`_execute_python_function_on_remote_windows` waits on the
`_vm_ready` threading.Event (defined at
`unity/function_manager/primitives/runtime.py:56`). That event is
set in production by either:
- ConversationManager startup
(`unity/conversation_manager/main.py:278`)
- ComputerPrimitives mock path
(`unity/function_manager/primitives/runtime.py:643`)
In a pure FunctionManager unit test neither codepath runs, so the
wait blocks until its 300s timeout fires.
Fix: pre-set the event in both `mock_session_details_windows` and
`mock_session_details_ubuntu` fixtures (the two fixtures any
remote_windows test ever uses). Capture the prior state so we
clear it again on teardown if WE flipped it on, leaving the
global event in its previous state for any sibling test that
happens to share the process.
This is the smallest surgical fix — alternatives like patching
the wait helper or refactoring `_execute_python_function_on_remote_windows`
to accept an injectable readiness signal would have a much wider
blast radius for no real benefit on the test side.
…nts, prompts, domains)
resolve_bot_token queried Orchestra with team_id and without include_token, so /slack/send could never fetch a token (400/503). Key on slack_team_id and request include_token. Add POST /slack/user-info (users.info) returning email + real/display name so the inbound pipeline can resolve an unknown Slack sender to a contact.
…name On first inbound from an unmapped slack_user_id, look up the sender via the gateway /slack/user-info and match an existing contact by email then by real/display name (ambiguity-refuse), persisting slack_user_id so later messages match directly. Addressed (@app) senders with no match get a respondable contact; others keep the gated unknown-contact policy. Breaks the bootstrap deadlock that dropped every first Slack message.
…dempotent A channel @mention is delivered twice (app_mention + message) with distinct event_ids but the same client_msg_id; dedup now keys on the stable message id so the pair collapses to one processed event. _create_slack_contact pre-checks and, on a lost unique-slack_user_id race, resolves to the existing contact instead of dropping the message.
| ) | ||
| logger.info( | ||
| "sent Slack message to %s on team %s (ts=%s)", | ||
| channel_id, |
| logger.info( | ||
| "sent Slack message to %s on team %s (ts=%s)", | ||
| channel_id, | ||
| team_id, |
…tact orphans Slack bot_user_id is workspace-scoped (on the install), so the per-assistant assistant_slack_bot_user_id is never set at bootstrap and the brain never gets the Slack send tools (always waits). Adopt the bot id from the inbound event when handling SlackMessageReceived/SlackChannelMessageReceived so the triggered brain run exposes send_slack_message/send_slack_channel_message. Also stop _create_slack_contact from minting a nameless, email-less contact that captures the slack_user_id and shadows the real contact; require a name or email and otherwise leave the sender for email/name resolution.
Surface the inbound message's event_ts as the effective thread_ts for Slack channel messages so a top-level @mention reply starts a thread instead of posting at the channel root. DMs keep prior behaviour and only thread when already threaded.
…ility demos Adds a collapsible README section illustrating six interactions the nested steerable-handle model enables (live deep redirect, live introspection, pause/inspect/resume, concurrent independent steering, clarification bubbling, and single-branch stop), each with its own generated diagram. Allow-lists the new demo images in .gitignore alongside the existing repo-shipped diagrams.
…e drifted) The container entrypoint only execed the headless offline runner when UNITY_OFFLINE_TASK_MODE == "function", but offline_runner_contract sets it to "actor" (pinned by tests/task_scheduler/test_offline_runner_contract.test_mode_is_actor). So the gate silently stopped matching: every offline scheduled/triggered task fell through to the live ConversationManager, which booted, idled on a startup reply, and exited WITHOUT executing its function — leaving the run row stale at "running" and never cloning the next recurrence. Match on any non-empty UNITY_OFFLINE_TASK_MODE so it's rename-proof. This entrypoint is baked into the unity base image; the unity-deploy/base/entrypoint.sh mirror is kept in sync.
OpenClaw and Hermes Agent both store skills as agentskills.io SKILL.md files (YAML frontmatter + markdown body + optional bundled scripts), which map near one-to-one onto GuidanceManager entries. This adds a reusable parsing/mapping core plus convenient openclaw_to_guidance and hermes_to_guidance CLIs so either library (~247 skills combined) can be imported off-the-shelf as guidance. The transfer is deliberately faithful: a skill's description + body become the guidance content and bundled scripts are inlined verbatim as textual reference (promoting them into runnable FunctionManager functions is left as a separate step). CLIs default to a dry run, are title-namespaced to avoid cross-repo collisions, and support skip/overwrite conflict handling on re-runs. Includes pure parsing tests and end-to-end GuidanceManager import tests, plus README docs.
Provide a reusable common helper for exact semantic top-k merging across multiple contexts by fetching offset+limit candidates per source, sorting by exposed score, and applying the final window once. This keeps future built-in-library reads from hand-rolling pagination and tie-breaking logic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes 6 commits from staging to main. Two themes plus one feature.
Open-source-readiness pass (3 commits)
aaabf3d46chore(repo): tighten .gitignore for build artifacts and add AGENTS.md.gitignorenow coversbuild/,dist/,*.egg-info/,Local/AGENTS.mddistilled from.cursor/rules/so Claude Code, Codex, Aider, Cline, etc. pick up the same conventions Cursor doesbfe44c46fchore(github): add CODEOWNERS, PR/issue templates, dependabot, OSV scannerCODEOWNERS—@unifyai/Engineersas catch-all + explicit ownership of security-sensitive pathsPULL_REQUEST_TEMPLATE.md— references the.cursor/rulesinvariantsISSUE_TEMPLATE/{config,bug_report,feature_request}.yml— routes bugs by surface; steers "please add this skill" feature requests towardGuidanceManager/FunctionManagerdependabot.yml— github-actions weekly (grouped) +agent-service/npm weekly; deliberately skips scheduled pip per the editable-sibling install modelworkflows/osv-scanner.yml— Google's reusable workflow pinned by SHA, SARIF to Security tabDependabot CVE triage (1 commit + 5 dismissals)
351563a81chore(deps): bump 9 packages to clear Dependabot CVE alertsurllib32.6.3 → 2.7.0 (CVE-2026-44431, CVE-2026-44432, both high)langchain-core1.3.0 → 1.4.0 (CVE-2026-44843 high)python-multipart0.0.26 → 0.0.29 (CVE-2026-42561 high)lxml6.0.3 → 6.1.1 (CVE-2026-41066 high)langsmith0.7.33 → 0.8.5 (CVE-2026-45134 high)authlib1.7.0 → 1.7.2 (CVE-2026-44681 medium)idna3.11 → 3.16 (CVE-2026-45409 medium)qs6.15.0 → 6.15.2 (CVE-2026-8723 medium, npm)ws8.18.3 → 8.21.0 (CVE-2026-45736 medium, npm)Plus 5 alerts dismissed out-of-band as
not_used: 4 LiteLLM proxy CVEs (Conversation Manager Refactor #69/add timestamp arg to clear_notifications #70/Summary #71/add assistant phone utterance to the thread #74 — proxy not deployed) and Replace explicit requests API calls to use Unify API #67 python-dotenv (we only read.env, never callset_key()). The litellm bump to 1.83.10 was investigated and rejected — it forcesopenai 2.30 → 2.24andlangchain-openai 1.1.15 → 1.1.10because litellm hard-pins openai. Recorded that decision via@dependabot ignore this versionon unillm#54.Net effect on the Security tab after this merge: 15 alerts (1 critical, 9 high, 5 medium) → 0.
Captcha primitive + docs (2 commits)
c9ba90982feat(computer): add solve_captcha primitive for reCAPTCHA v2 via AntiCaptcha39fe85099docs(env): document ANTICAPTCHA_KEY placeholder in .env.exampleOther in-flight work picked up incidentally
bd001c346test(task_scheduler): pin Communication env-builder equivalence in shared contract tests — landed on staging before this session.Test plan
The full test suite auto-runs on staging→main PRs (
tests.ymlline 130). No tags needed. Auto-merge on green.