-
Notifications
You must be signed in to change notification settings - Fork 88
meet: wire chat-opportunity detector to agent-wake in session lifecycle #25949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -74,8 +74,9 @@ export interface WakeResult { | |||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Dependencies injected for testing. Production callers use the defaults | ||||||||||||||||||||||||||||||||||||||||||||||||
| * (which resolve the conversation from the daemon's registry). | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Dependencies injected for testing. Production callers can omit this | ||||||||||||||||||||||||||||||||||||||||||||||||
| * argument entirely and rely on a process-wide default resolver registered | ||||||||||||||||||||||||||||||||||||||||||||||||
| * at daemon startup via {@link registerDefaultWakeResolver}. | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export interface WakeDeps { | ||||||||||||||||||||||||||||||||||||||||||||||||
| /** Resolve the wake target for a conversationId. Returns `null` if not found. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -84,6 +85,49 @@ export interface WakeDeps { | |||||||||||||||||||||||||||||||||||||||||||||||
| now?: () => number; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // ── Process-wide default resolver ──────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||
| // PR 6 shipped `wakeAgentForOpportunity` with a required `deps` argument | ||||||||||||||||||||||||||||||||||||||||||||||||
| // carrying an explicit `resolveTarget`. PR 7 needs to call the helper | ||||||||||||||||||||||||||||||||||||||||||||||||
| // from code paths (e.g. `MeetSessionManager.join`) that don't know how | ||||||||||||||||||||||||||||||||||||||||||||||||
| // to build a `WakeTarget` — the adapter that wraps a live `Conversation` | ||||||||||||||||||||||||||||||||||||||||||||||||
| // lives in the daemon, not the skill. To avoid importing daemon code | ||||||||||||||||||||||||||||||||||||||||||||||||
| // into `runtime/agent-wake.ts` (and the skill bundle that wires | ||||||||||||||||||||||||||||||||||||||||||||||||
| // proactive-chat into the manager), we expose a module-level default | ||||||||||||||||||||||||||||||||||||||||||||||||
| // resolver that the daemon installs once at startup. Callers that don't | ||||||||||||||||||||||||||||||||||||||||||||||||
| // pass explicit `deps` fall back to it. Tests that pass explicit deps | ||||||||||||||||||||||||||||||||||||||||||||||||
| // are unaffected — the default is never consulted when deps are | ||||||||||||||||||||||||||||||||||||||||||||||||
| // supplied. | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+88
to
+100
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Comment narrates PR history, violating assistant/AGENTS.md comment rule The block comment at
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| let _defaultResolver: | ||||||||||||||||||||||||||||||||||||||||||||||||
| | ((conversationId: string) => Promise<WakeTarget | null>) | ||||||||||||||||||||||||||||||||||||||||||||||||
| | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Install the process-wide default resolver. Called once at daemon | ||||||||||||||||||||||||||||||||||||||||||||||||
| * startup (see `DaemonServer.start()`) with an adapter that looks up a | ||||||||||||||||||||||||||||||||||||||||||||||||
| * live {@link Conversation} and wraps it as a {@link WakeTarget}. | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Calling this more than once replaces the prior resolver — the daemon | ||||||||||||||||||||||||||||||||||||||||||||||||
| * startup path should call it exactly once, but tests that want to | ||||||||||||||||||||||||||||||||||||||||||||||||
| * exercise the default path can register a mock and reset via | ||||||||||||||||||||||||||||||||||||||||||||||||
| * {@link resetDefaultWakeResolverForTests}. | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export function registerDefaultWakeResolver( | ||||||||||||||||||||||||||||||||||||||||||||||||
| resolver: (conversationId: string) => Promise<WakeTarget | null>, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ): void { | ||||||||||||||||||||||||||||||||||||||||||||||||
| _defaultResolver = resolver; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Reset the process-wide default resolver. Test-only. | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * @internal | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export function resetDefaultWakeResolverForTests(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||
| _defaultResolver = null; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // ── Per-conversation single-flight lock ─────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Simple promise-chain map. When a wake arrives and another run is in | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -183,17 +227,30 @@ function inspectAssistantOutput( | |||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * See module-level doc for semantics. Safe to call concurrently; wakes | ||||||||||||||||||||||||||||||||||||||||||||||||
| * are serialized per `conversationId`. | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * The `deps` argument is optional in production — when omitted, the | ||||||||||||||||||||||||||||||||||||||||||||||||
| * process-wide resolver registered by | ||||||||||||||||||||||||||||||||||||||||||||||||
| * {@link registerDefaultWakeResolver} is used. Tests that want tight | ||||||||||||||||||||||||||||||||||||||||||||||||
| * control over resolution continue to pass explicit deps. | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export async function wakeAgentForOpportunity( | ||||||||||||||||||||||||||||||||||||||||||||||||
| opts: WakeOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||
| deps: WakeDeps, | ||||||||||||||||||||||||||||||||||||||||||||||||
| deps?: WakeDeps, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<WakeResult> { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { conversationId, hint, source } = opts; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const nowFn = deps.now ?? Date.now; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const resolveTarget = deps?.resolveTarget ?? _defaultResolver; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!resolveTarget) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn( | ||||||||||||||||||||||||||||||||||||||||||||||||
| { conversationId, source }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| "agent-wake: no resolver available (default resolver not registered and no deps passed); skipping", | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return { invoked: false, producedToolCalls: false }; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| const nowFn = deps?.now ?? Date.now; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const startedAt = nowFn(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return runSingleFlight(conversationId, async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const target = await deps.resolveTarget(conversationId); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const target = await resolveTarget(conversationId); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!target) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn( | ||||||||||||||||||||||||||||||||||||||||||||||||
| { conversationId, source }, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default resolver uses
getOrCreateConversation(conversationId), so a stale/invalid wake target does not resolve tonullaswakeAgentForOpportunityexpects; it creates a new conversation and proceeds. This is risky in late-callback or deleted-conversation scenarios (for example, an opportunity arriving after meeting teardown), because it can spawn a ghost conversation and run an unintended LLM/tool turn instead of safely skipping. The resolver should resolve an existing conversation (or verify DB existence) and returnnullwhen the target is gone.Useful? React with 👍 / 👎.