@indexnetwork/protocol 3.7.0-rc.276.1 → 3.7.1-rc.278.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.tools.js +1 -1
- package/dist/agent/agent.tools.js.map +1 -1
- package/dist/chat/chat.agent.d.ts +6 -6
- package/dist/chat/chat.agent.d.ts.map +1 -1
- package/dist/chat/chat.agent.js +9 -9
- package/dist/chat/chat.agent.js.map +1 -1
- package/dist/chat/chat.graph.d.ts.map +1 -1
- package/dist/chat/chat.graph.js +3 -12
- package/dist/chat/chat.graph.js.map +1 -1
- package/dist/chat/chat.interrupt.classifier.d.ts.map +1 -1
- package/dist/chat/chat.interrupt.classifier.js +1 -3
- package/dist/chat/chat.interrupt.classifier.js.map +1 -1
- package/dist/chat/chat.suggester.js.map +1 -1
- package/dist/contact/contact.tools.d.ts.map +1 -1
- package/dist/contact/contact.tools.js +17 -5
- package/dist/contact/contact.tools.js.map +1 -1
- package/dist/context/context.generator.d.ts +2 -0
- package/dist/context/context.generator.d.ts.map +1 -1
- package/dist/context/context.generator.js +8 -6
- package/dist/context/context.generator.js.map +1 -1
- package/dist/integration/integration.tools.d.ts.map +1 -1
- package/dist/integration/integration.tools.js +6 -0
- package/dist/integration/integration.tools.js.map +1 -1
- package/dist/intent/intent.clarifier.d.ts +2 -0
- package/dist/intent/intent.clarifier.d.ts.map +1 -1
- package/dist/intent/intent.clarifier.js +9 -23
- package/dist/intent/intent.clarifier.js.map +1 -1
- package/dist/intent/intent.graph.d.ts.map +1 -1
- package/dist/intent/intent.graph.js +29 -26
- package/dist/intent/intent.graph.js.map +1 -1
- package/dist/intent/intent.tools.d.ts.map +1 -1
- package/dist/intent/intent.tools.js +18 -48
- package/dist/intent/intent.tools.js.map +1 -1
- package/dist/maintenance/maintenance.graph.d.ts.map +1 -1
- package/dist/maintenance/maintenance.graph.js +1 -2
- package/dist/maintenance/maintenance.graph.js.map +1 -1
- package/dist/mcp/mcp.server.d.ts.map +1 -1
- package/dist/mcp/mcp.server.js +2 -4
- package/dist/mcp/mcp.server.js.map +1 -1
- package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
- package/dist/negotiation/negotiation.graph.js +13 -20
- package/dist/negotiation/negotiation.graph.js.map +1 -1
- package/dist/negotiation/negotiation.tools.d.ts.map +1 -1
- package/dist/negotiation/negotiation.tools.js +12 -12
- package/dist/negotiation/negotiation.tools.js.map +1 -1
- package/dist/network/indexer/indexer.graph.d.ts +9 -9
- package/dist/network/indexer/indexer.graph.d.ts.map +1 -1
- package/dist/network/indexer/indexer.graph.js.map +1 -1
- package/dist/network/network.graph.d.ts.map +1 -1
- package/dist/network/network.graph.js +19 -25
- package/dist/network/network.graph.js.map +1 -1
- package/dist/opportunity/feed/feed.categorizer.d.ts.map +1 -1
- package/dist/opportunity/feed/feed.categorizer.js +15 -20
- package/dist/opportunity/feed/feed.categorizer.js.map +1 -1
- package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
- package/dist/opportunity/feed/feed.graph.js +8 -10
- package/dist/opportunity/feed/feed.graph.js.map +1 -1
- package/dist/opportunity/opportunity.introducer.d.ts.map +1 -1
- package/dist/opportunity/opportunity.introducer.js +1 -2
- package/dist/opportunity/opportunity.introducer.js.map +1 -1
- package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
- package/dist/opportunity/opportunity.tools.js +3 -2
- package/dist/opportunity/opportunity.tools.js.map +1 -1
- package/dist/profile/profile.enricher.d.ts +5 -7
- package/dist/profile/profile.enricher.d.ts.map +1 -1
- package/dist/profile/profile.enricher.js +8 -10
- package/dist/profile/profile.enricher.js.map +1 -1
- package/dist/profile/profile.generator.d.ts.map +1 -1
- package/dist/profile/profile.generator.js +1 -2
- package/dist/profile/profile.generator.js.map +1 -1
- package/dist/profile/profile.tools.js +1 -1
- package/dist/profile/profile.tools.js.map +1 -1
- package/dist/questioner/questioner.presets.d.ts.map +1 -1
- package/dist/questioner/questioner.presets.js +24 -38
- package/dist/questioner/questioner.presets.js.map +1 -1
- package/dist/shared/agent/tool.factory.d.ts.map +1 -1
- package/dist/shared/agent/tool.factory.js +2 -2
- package/dist/shared/agent/tool.factory.js.map +1 -1
- package/dist/shared/agent/tool.helpers.d.ts +14 -0
- package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
- package/dist/shared/agent/tool.helpers.js.map +1 -1
- package/dist/shared/agent/tool.runtime.d.ts.map +1 -1
- package/dist/shared/agent/tool.runtime.js +20 -13
- package/dist/shared/agent/tool.runtime.js.map +1 -1
- package/dist/shared/hyde/hyde.graph.d.ts.map +1 -1
- package/dist/shared/hyde/hyde.graph.js +3 -2
- package/dist/shared/hyde/hyde.graph.js.map +1 -1
- package/dist/shared/hyde/hyde.strategies.d.ts +2 -1
- package/dist/shared/hyde/hyde.strategies.d.ts.map +1 -1
- package/dist/shared/hyde/hyde.strategies.js.map +1 -1
- package/dist/shared/observability/trace.d.ts +3 -3
- package/dist/shared/observability/trace.d.ts.map +1 -1
- package/dist/shared/observability/trace.js +19 -33
- package/dist/shared/observability/trace.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.interrupt.classifier.js","sourceRoot":"/","sources":["chat/chat.interrupt.classifier.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAAE,GAAG,EAAE,MAAM,gCAAgC,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;AAEvD,MAAM,aAAa,GAAG;;;;;;oCAMc,CAAC;AAYrC;;;;GAIG;AACH,MAAM,OAAO,uBAAuB;IAGlC;QACE,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAClD,CAAC;IAED;;;;;OAKG;IAEG,AAAN,KAAK,CAAC,QAAQ,CAAC,KAA6B;QAC1C,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;QAEtC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE;gBACvD,IAAI,aAAa,CAAC,aAAa,CAAC;gBAChC,IAAI,YAAY,CACd,2BAA2B,UAAU,IAAI,MAAM,0BAA0B,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAC/G;aACF,CAAC,CAAC;YAEH,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"chat.interrupt.classifier.js","sourceRoot":"/","sources":["chat/chat.interrupt.classifier.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAAE,GAAG,EAAE,MAAM,gCAAgC,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;AAEvD,MAAM,aAAa,GAAG;;;;;;oCAMc,CAAC;AAYrC;;;;GAIG;AACH,MAAM,OAAO,uBAAuB;IAGlC;QACE,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAClD,CAAC;IAED;;;;;OAKG;IAEG,AAAN,KAAK,CAAC,QAAQ,CAAC,KAA6B;QAC1C,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;QAEtC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE;gBACvD,IAAI,aAAa,CAAC,aAAa,CAAC;gBAChC,IAAI,YAAY,CACd,2BAA2B,UAAU,IAAI,MAAM,0BAA0B,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAC/G;aACF,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAEjE,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAAE,OAAO,OAAO,CAAC;YAC7C,yDAAyD;YACzD,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,+EAA+E,EAAE;gBAC3F,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAC;YACH,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;CACF;AAvBO;IADL,KAAK,EAAE;;;;uDAuBP","sourcesContent":["import type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\n\nimport { log } from \"../shared/observability/log.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = log.lib.from(\"ChatInterruptClassifier\");\n\nconst SYSTEM_PROMPT = `You decide whether a new user message sent during an active AI process should STEER or QUEUE.\n\nRules:\n- Reply with ONLY one word: steer or queue (lowercase).\n- STEER: the message redirects, corrects, stops, contradicts, or changes what the AI is doing. Keywords: \"wait\", \"stop\", \"actually\", \"ignore that\", \"instead\", \"no\", \"cancel\".\n- QUEUE: the message adds context, asks a follow-up, or complements what the AI is doing. Keywords: \"also\", \"and\", \"when done\", \"additionally\", \"plus\".\n- When ambiguous, default to steer.`;\n\nexport interface ClassifyInterruptInput {\n /** The new user message sent while the agent is running. */\n message: string;\n /**\n * Current agent activity summary derived from the last few SSE trace event names\n * (e.g. \"tool_start: discover_opportunities, graph_start: opportunity\").\n */\n agentState: string;\n}\n\n/**\n * Binary classifier that decides whether a mid-stream user message should steer\n * (interrupt the current run) or queue (buffer until the run completes).\n * Uses a low-temperature, minimal-token model for sub-1 s latency.\n */\nexport class ChatInterruptClassifier {\n private model: ChatOpenAI;\n\n constructor() {\n this.model = createModel(\"interruptClassifier\");\n }\n\n /**\n * Classify a mid-stream interrupt as steer or queue.\n *\n * @param input - The new message and current agent state context\n * @returns \"steer\" to interrupt the current run; \"queue\" to buffer\n */\n @Timed()\n async classify(input: ClassifyInterruptInput): Promise<\"steer\" | \"queue\"> {\n const { message, agentState } = input;\n\n try {\n const response = await invokeWithAbortSignal(this.model, [\n new SystemMessage(SYSTEM_PROMPT),\n new HumanMessage(\n `Current agent activity: ${agentState || \"idle\"}\\n\\nNew user message: \"${message.slice(0, 500)}\"\\n\\nDecision:`,\n ),\n ]);\n\n const text = String(response.content ?? \"\").trim().toLowerCase();\n\n if (text.startsWith(\"queue\")) return \"queue\";\n // Default to steer on any ambiguity or unexpected output\n return \"steer\";\n } catch (error) {\n logger.warn(\"[ChatInterruptClassifier.classify] Classification failed, defaulting to steer\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return \"steer\";\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.suggester.js","sourceRoot":"/","sources":["chat/chat.suggester.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"chat.suggester.js","sourceRoot":"/","sources":["chat/chat.suggester.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,MAAM,GAAG,cAAc,CAAC,qBAAqB,CAAC,CAAC;AAErD,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IAClE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,sDAAsD,CAAC;IACnG,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;IACxH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kEAAkE,CAAC;CAC5G,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,WAAW,EAAE,CAAC;SACX,KAAK,CAAC,oBAAoB,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,qDAAqD,CAAC;CACnE,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG;;;;;;;;;sLASgK,CAAC;AASvL;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IAG9B;QACE,MAAM,GAAG,GAAG,WAAW,CAAC,qBAAqB,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,oBAAoB,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;IACzF,CAAC;IAED;;;OAGG;IAEG,AAAN,KAAK,CAAC,QAAQ,CAAC,KAA+B;QAC5C,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;QACzC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAErC,MAAM,OAAO,GAAG,QAAQ;aACrB,KAAK,CAAC,CAAC,CAAC,CAAC;aACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;aACrF,IAAI,CAAC,MAAM,CAAC,CAAC;QAEhB,MAAM,WAAW,GAAG,YAAY;YAC9B,CAAC,CAAC,oCAAoC,YAAY,SAAS,OAAO,yCAAyC;YAC3G,CAAC,CAAC,oBAAoB,OAAO,yCAAyC,CAAC;QAEzE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE;gBACrD,IAAI,aAAa,CAAC,aAAa,CAAC;gBAChC,IAAI,YAAY,CAAC,WAAW,CAAC;aAC9B,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,CAAC,oCAAoC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBACnF,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,GAAG,GAAqB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAChE,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC;gBACtF,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;aACxE,CAAC,CAAC,CAAC;YACJ,MAAM,CAAC,OAAO,CAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACzE,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE;gBAC1C,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAtCO;IADL,KAAK,EAAE;;;;mDAsCP","sourcesContent":["import type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\nimport type { ChatSuggestion } from \"./chat-streaming.types.js\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"SuggestionGenerator\");\n\nconst suggestionItemSchema = z.object({\n label: z.string().describe(\"Short label for the chip (2-5 words)\"),\n type: z.enum([\"direct\", \"prompt\"]).describe(\"direct = auto-submit message; prompt = prefill input\"),\n followupText: z.string().nullable().describe(\"For type=direct: full message to send when clicked; null for prompt type\"),\n prefill: z.string().nullable().describe(\"For type=prompt: text to prefill the input; null for direct type\"),\n});\n\nconst suggestionsSchema = z.object({\n suggestions: z\n .array(suggestionItemSchema)\n .min(1)\n .max(6)\n .describe(\"3-5 follow-up suggestions based on the conversation\"),\n});\n\nconst SYSTEM_PROMPT = `You generate follow-up suggestions for a chat user based on their conversation.\n\nRules:\n- Return 3-5 suggestions. Mix of \"direct\" (one-click send) and \"prompt\" (prefill for user to edit).\n- Labels must be short: 2-5 words (e.g. \"Find collaborators\", \"Add more details\").\n- For type=direct: provide followupText as the exact message to send (complete sentence).\n- For type=prompt: provide prefill as the start of a sentence the user can complete (e.g. \"I need help with \").\n- Suggestions must be relevant to the last exchange and natural next steps.\n- Do not repeat what the user or assistant just said; suggest logical follow-ups.\n- Voice: Calm, direct; no hype or networking clichés. Prefer words like opportunity, overlap, signal, pattern, relevant. Avoid: search, leverage, networking, match, optimize, scale.`;\n\nexport interface SuggestionGeneratorInput {\n /** Last few messages (user and assistant) to derive context */\n messages: Array<{ role: \"user\" | \"assistant\"; content: string }>;\n /** Optional index/community context to tailor suggestions */\n indexContext?: string;\n}\n\n/**\n * Lightweight generator for context-aware chat follow-up suggestions.\n * Uses a fast model and structured output to return 3-5 suggestions per call.\n */\nexport class SuggestionGenerator {\n private model: ReturnType<ChatOpenAI[\"withStructuredOutput\"]>;\n\n constructor() {\n const llm = createModel(\"suggestionGenerator\");\n this.model = llm.withStructuredOutput(suggestionsSchema, { name: \"chat_suggestions\" });\n }\n\n /**\n * Generate follow-up suggestions from the last exchange.\n * Returns empty array on failure (graceful degradation).\n */\n @Timed()\n async generate(input: SuggestionGeneratorInput): Promise<ChatSuggestion[]> {\n const { messages, indexContext } = input;\n if (messages.length === 0) return [];\n\n const excerpt = messages\n .slice(-6)\n .map((m) => `${m.role === \"user\" ? \"User\" : \"Assistant\"}: ${m.content.slice(0, 300)}`)\n .join(\"\\n\\n\");\n\n const userContent = indexContext\n ? `Conversation (community context: ${indexContext}):\\n\\n${excerpt}\\n\\nGenerate 3-5 follow-up suggestions.`\n : `Conversation:\\n\\n${excerpt}\\n\\nGenerate 3-5 follow-up suggestions.`;\n\n try {\n const result = await invokeWithAbortSignal(this.model, [\n new SystemMessage(SYSTEM_PROMPT),\n new HumanMessage(userContent),\n ]);\n const parsed = suggestionsSchema.safeParse(result);\n if (!parsed.success) {\n logger.warn(\"[SuggestionGenerator] Parse failed\", { error: parsed.error.message });\n return [];\n }\n const out: ChatSuggestion[] = parsed.data.suggestions.map((s) => ({\n label: s.label,\n type: s.type,\n ...(s.type === \"direct\" && s.followupText != null && { followupText: s.followupText }),\n ...(s.type === \"prompt\" && s.prefill != null && { prefill: s.prefill }),\n }));\n logger.verbose(\"[SuggestionGenerator] Generated\", { count: out.length });\n return out;\n } catch (error) {\n logger.warn(\"[SuggestionGenerator] Failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return [];\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contact.tools.d.ts","sourceRoot":"/","sources":["contact/contact.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAM5E;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"contact.tools.d.ts","sourceRoot":"/","sources":["contact/contact.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAM5E;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,SAqLxE"}
|
|
@@ -8,7 +8,13 @@ const logger = protocolLogger('ContactTools');
|
|
|
8
8
|
*/
|
|
9
9
|
export function createContactTools(defineTool, deps) {
|
|
10
10
|
const { contactService } = deps;
|
|
11
|
-
|
|
11
|
+
// Contact import / manual-add create ghost users. These are gated behind the
|
|
12
|
+
// CONTACTS_ENABLED flag (injected as deps.contactsEnabled). Read/remove/search
|
|
13
|
+
// tools below are always available so existing contacts stay manageable.
|
|
14
|
+
const contactsEnabled = deps.contactsEnabled === true;
|
|
15
|
+
// Only register when enabled: in the registry path defineTool registers as a
|
|
16
|
+
// side effect, so the call itself must be gated, not just the returned array.
|
|
17
|
+
const import_contacts = contactsEnabled ? defineTool({
|
|
12
18
|
name: 'import_contacts',
|
|
13
19
|
description: "Bulk-imports contacts into the authenticated user's personal network (personal index). Contacts become members of the user's " +
|
|
14
20
|
"personal index with 'contact' permission, making them available for opportunity discovery.\n\n" +
|
|
@@ -41,7 +47,7 @@ export function createContactTools(defineTool, deps) {
|
|
|
41
47
|
return error('Failed to import contacts. Please try again.');
|
|
42
48
|
}
|
|
43
49
|
},
|
|
44
|
-
});
|
|
50
|
+
}) : null;
|
|
45
51
|
const list_contacts = defineTool({
|
|
46
52
|
name: 'list_contacts',
|
|
47
53
|
description: "Lists all contacts in the authenticated user's personal network. Contacts are people the user has added " +
|
|
@@ -77,7 +83,7 @@ export function createContactTools(defineTool, deps) {
|
|
|
77
83
|
}
|
|
78
84
|
},
|
|
79
85
|
});
|
|
80
|
-
const add_contact = defineTool({
|
|
86
|
+
const add_contact = contactsEnabled ? defineTool({
|
|
81
87
|
name: 'add_contact',
|
|
82
88
|
description: "Adds a single contact to the authenticated user's personal network by email address. " +
|
|
83
89
|
"For bulk imports, use import_contacts instead.\n\n" +
|
|
@@ -108,7 +114,7 @@ export function createContactTools(defineTool, deps) {
|
|
|
108
114
|
return error('Failed to add contact. Please try again.');
|
|
109
115
|
}
|
|
110
116
|
},
|
|
111
|
-
});
|
|
117
|
+
}) : null;
|
|
112
118
|
const remove_contact = defineTool({
|
|
113
119
|
name: 'remove_contact',
|
|
114
120
|
description: "Removes a contact from the authenticated user's personal network. The contact relationship is deleted — " +
|
|
@@ -162,6 +168,12 @@ export function createContactTools(defineTool, deps) {
|
|
|
162
168
|
}
|
|
163
169
|
},
|
|
164
170
|
});
|
|
165
|
-
return [
|
|
171
|
+
return [
|
|
172
|
+
...(import_contacts ? [import_contacts] : []),
|
|
173
|
+
...(add_contact ? [add_contact] : []),
|
|
174
|
+
list_contacts,
|
|
175
|
+
remove_contact,
|
|
176
|
+
search_contacts,
|
|
177
|
+
];
|
|
166
178
|
}
|
|
167
179
|
//# sourceMappingURL=contact.tools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contact.tools.js","sourceRoot":"/","sources":["contact/contact.tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { DefineTool, ToolDeps } from '../shared/agent/tool.helpers.js';\nimport { success, error } from '../shared/agent/tool.helpers.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('ContactTools');\n\n/**\n * Creates contact management tools for the chat agent.\n * Enables importing, listing, and managing the user's network.\n */\nexport function createContactTools(defineTool: DefineTool, deps: ToolDeps) {\n const { contactService } = deps;\n\n const import_contacts = defineTool({\n name: 'import_contacts',\n description:\n \"Bulk-imports contacts into the authenticated user's personal network (personal index). Contacts become members of the user's \" +\n \"personal index with 'contact' permission, making them available for opportunity discovery.\\n\\n\" +\n \"**What happens:** Each contact is matched by email. If the email belongs to an existing user, they're linked directly. \" +\n \"If not, a 'ghost user' is created — a placeholder account enriched with public profile data (from LinkedIn, GitHub, etc.) \" +\n \"that participates in opportunity matching even before the person joins the platform.\\n\\n\" +\n \"**When to use:** When the user provides a list of contacts to add (from CSV, manual input, or any source other than Gmail). \" +\n \"For Gmail specifically, use import_gmail_contacts instead.\\n\\n\" +\n \"**Returns:** Import statistics: imported (total processed), skipped (invalid), newContacts (ghost users created), \" +\n \"existingContacts (already in network). Use list_contacts to see all contacts after import.\",\n querySchema: z.object({\n contacts: z.array(z.object({\n name: z.string().describe('Full name of the contact (e.g. \"Jane Smith\")'),\n email: z.string().describe('Email address — used as the unique identifier for matching existing users'),\n })).describe('Array of contact objects to import. Each must have name and email. Duplicates (by email) are skipped.'),\n }),\n handler: async ({ context, query }) => {\n try {\n const result = await contactService.importContacts(\n context.userId,\n query.contacts\n );\n return success({\n message: `Imported ${result.imported} contacts to your network.`,\n imported: result.imported,\n skipped: result.skipped,\n newContacts: result.newContacts,\n existingContacts: result.existingContacts,\n });\n } catch (err) {\n logger.error('Failed to import contacts', { err });\n return error('Failed to import contacts. Please try again.');\n }\n },\n });\n\n const list_contacts = defineTool({\n name: 'list_contacts',\n description:\n \"Lists all contacts in the authenticated user's personal network. Contacts are people the user has added \" +\n \"(via import_contacts, add_contact, or import_gmail_contacts) stored as members of their personal index.\\n\\n\" +\n \"**When to use:** To see who's in the user's network, find a contact's userId for other operations, \" +\n \"or check if a specific person is already a contact.\\n\\n\" +\n \"**Returns:** Array of contacts, each with: userId (use with read_user_profiles or discover_opportunities), \" +\n \"name, email, avatar URL, and isGhost (true = no account yet, profile enriched from public data). \" +\n \"Use the userId with read_user_profiles(userId) to get the full profile, or with discover_opportunities(targetUserId) to connect.\",\n querySchema: z.object({\n limit: z.number().optional().describe('Maximum number of contacts to return. Omit to return all contacts. Use for large networks to paginate results.'),\n }),\n handler: async ({ context, query }) => {\n try {\n let contacts = await contactService.listContacts(context.userId);\n\n if (query.limit && query.limit > 0) {\n contacts = contacts.slice(0, query.limit);\n }\n\n return success({\n count: contacts.length,\n contacts: contacts.map(c => ({\n userId: c.userId,\n name: c.user.name,\n email: c.user.email,\n avatar: c.user.avatar,\n isGhost: c.user.isGhost,\n })),\n });\n } catch (err) {\n logger.error('Failed to list contacts', { err });\n return error('Failed to list contacts. Please try again.');\n }\n },\n });\n\n const add_contact = defineTool({\n name: 'add_contact',\n description:\n \"Adds a single contact to the authenticated user's personal network by email address. \" +\n \"For bulk imports, use import_contacts instead.\\n\\n\" +\n \"**What happens:** Looks up the email. If an account exists, links that user as a contact. \" +\n \"If not, creates a ghost user (placeholder enriched with public profile data) and adds them. \" +\n \"The contact can then appear in opportunity discovery within the user's personal index.\\n\\n\" +\n \"**When to use:** When the user wants to add a specific person (e.g. 'add john@example.com to my network').\\n\\n\" +\n \"**Returns:** Confirmation with the contact's userId and whether a new ghost user was created (isNewGhost). \" +\n \"Use the userId with discover_opportunities(targetUserId) to find connection opportunities.\",\n querySchema: z.object({\n email: z.string().describe('Email address of the person to add. Used as unique identifier — if already a contact, the operation is idempotent.'),\n name: z.string().optional().describe('Full name of the contact. Optional — if omitted, the email prefix is used as name. Provide when known for better profile enrichment.'),\n }),\n handler: async ({ context, query }) => {\n try {\n const result = await contactService.addContact(context.userId, query.email, { name: query.name, restore: true });\n\n return success({\n added: true,\n message: result.isNew\n ? `Added ${query.name || query.email} to your network. Their profile is being enriched.`\n : `Added ${query.name || query.email} to your network.`,\n userId: result.userId,\n isNewGhost: result.isNew,\n });\n } catch (err) {\n logger.error('Failed to add contact', { err });\n return error('Failed to add contact. Please try again.');\n }\n },\n });\n\n const remove_contact = defineTool({\n name: 'remove_contact',\n description:\n \"Removes a contact from the authenticated user's personal network. The contact relationship is deleted — \" +\n \"the person is no longer a member of the user's personal index and won't appear in personal-index-scoped discovery.\\n\\n\" +\n \"**When to use:** When the user wants to remove someone from their network (e.g. 'remove John from my contacts').\\n\\n\" +\n \"**Note:** This only removes the contact relationship. If the contact is a real user (not a ghost), \" +\n \"they still exist on the platform and may appear in shared index discovery.\\n\\n\" +\n \"**Returns:** Confirmation that the contact was removed.\",\n querySchema: z.object({\n contactUserId: z.string().describe('The userId of the contact to remove. Get this from list_contacts results.'),\n }),\n handler: async ({ context, query }) => {\n try {\n await contactService.removeContact(context.userId, query.contactUserId);\n return success({ removed: true, message: 'Contact removed from your network.' });\n } catch (err) {\n logger.error('Failed to remove contact', { err });\n return error('Failed to remove contact. Please try again.');\n }\n },\n });\n\n const search_contacts = defineTool({\n name: 'search_contacts',\n description:\n \"Searches the authenticated user's personal network by name or email (case-insensitive substring). \" +\n \"Use when the user refers to a contact by partial name or email and you need their userId for another tool \" +\n \"(e.g. read_user_profiles, discover_opportunities).\\n\\n\" +\n \"**When to use:** Before list_contacts when the network is large — returns only matching contacts, bounded by limit.\\n\\n\" +\n \"**Returns:** Array of matching contacts: userId, name, email, avatar, isGhost.\",\n querySchema: z.object({\n query: z.string().trim().min(1).describe('Free-text query matched against contact name and email (case-insensitive, substring).'),\n limit: z.number().int().positive().max(100).optional().describe('Maximum rows to return. Defaults to 25.'),\n }),\n handler: async ({ context, query }) => {\n try {\n const rows = await contactService.searchContacts(context.userId, query.query, query.limit ?? 25);\n return success({\n count: rows.length,\n contacts: rows.map(r => ({\n userId: r.contactId,\n name: r.name,\n email: r.email,\n avatar: r.avatar,\n isGhost: r.isGhost,\n })),\n });\n } catch (err) {\n logger.error('Failed to search contacts', { err });\n return error('Failed to search contacts. Please try again.');\n }\n },\n });\n\n return [import_contacts, list_contacts, add_contact, remove_contact, search_contacts];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"contact.tools.js","sourceRoot":"/","sources":["contact/contact.tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { DefineTool, ToolDeps } from '../shared/agent/tool.helpers.js';\nimport { success, error } from '../shared/agent/tool.helpers.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('ContactTools');\n\n/**\n * Creates contact management tools for the chat agent.\n * Enables importing, listing, and managing the user's network.\n */\nexport function createContactTools(defineTool: DefineTool, deps: ToolDeps) {\n const { contactService } = deps;\n // Contact import / manual-add create ghost users. These are gated behind the\n // CONTACTS_ENABLED flag (injected as deps.contactsEnabled). Read/remove/search\n // tools below are always available so existing contacts stay manageable.\n const contactsEnabled = deps.contactsEnabled === true;\n\n // Only register when enabled: in the registry path defineTool registers as a\n // side effect, so the call itself must be gated, not just the returned array.\n const import_contacts = contactsEnabled ? defineTool({\n name: 'import_contacts',\n description:\n \"Bulk-imports contacts into the authenticated user's personal network (personal index). Contacts become members of the user's \" +\n \"personal index with 'contact' permission, making them available for opportunity discovery.\\n\\n\" +\n \"**What happens:** Each contact is matched by email. If the email belongs to an existing user, they're linked directly. \" +\n \"If not, a 'ghost user' is created — a placeholder account enriched with public profile data (from LinkedIn, GitHub, etc.) \" +\n \"that participates in opportunity matching even before the person joins the platform.\\n\\n\" +\n \"**When to use:** When the user provides a list of contacts to add (from CSV, manual input, or any source other than Gmail). \" +\n \"For Gmail specifically, use import_gmail_contacts instead.\\n\\n\" +\n \"**Returns:** Import statistics: imported (total processed), skipped (invalid), newContacts (ghost users created), \" +\n \"existingContacts (already in network). Use list_contacts to see all contacts after import.\",\n querySchema: z.object({\n contacts: z.array(z.object({\n name: z.string().describe('Full name of the contact (e.g. \"Jane Smith\")'),\n email: z.string().describe('Email address — used as the unique identifier for matching existing users'),\n })).describe('Array of contact objects to import. Each must have name and email. Duplicates (by email) are skipped.'),\n }),\n handler: async ({ context, query }) => {\n try {\n const result = await contactService.importContacts(\n context.userId,\n query.contacts\n );\n return success({\n message: `Imported ${result.imported} contacts to your network.`,\n imported: result.imported,\n skipped: result.skipped,\n newContacts: result.newContacts,\n existingContacts: result.existingContacts,\n });\n } catch (err) {\n logger.error('Failed to import contacts', { err });\n return error('Failed to import contacts. Please try again.');\n }\n },\n }) : null;\n\n const list_contacts = defineTool({\n name: 'list_contacts',\n description:\n \"Lists all contacts in the authenticated user's personal network. Contacts are people the user has added \" +\n \"(via import_contacts, add_contact, or import_gmail_contacts) stored as members of their personal index.\\n\\n\" +\n \"**When to use:** To see who's in the user's network, find a contact's userId for other operations, \" +\n \"or check if a specific person is already a contact.\\n\\n\" +\n \"**Returns:** Array of contacts, each with: userId (use with read_user_profiles or discover_opportunities), \" +\n \"name, email, avatar URL, and isGhost (true = no account yet, profile enriched from public data). \" +\n \"Use the userId with read_user_profiles(userId) to get the full profile, or with discover_opportunities(targetUserId) to connect.\",\n querySchema: z.object({\n limit: z.number().optional().describe('Maximum number of contacts to return. Omit to return all contacts. Use for large networks to paginate results.'),\n }),\n handler: async ({ context, query }) => {\n try {\n let contacts = await contactService.listContacts(context.userId);\n\n if (query.limit && query.limit > 0) {\n contacts = contacts.slice(0, query.limit);\n }\n\n return success({\n count: contacts.length,\n contacts: contacts.map(c => ({\n userId: c.userId,\n name: c.user.name,\n email: c.user.email,\n avatar: c.user.avatar,\n isGhost: c.user.isGhost,\n })),\n });\n } catch (err) {\n logger.error('Failed to list contacts', { err });\n return error('Failed to list contacts. Please try again.');\n }\n },\n });\n\n const add_contact = contactsEnabled ? defineTool({\n name: 'add_contact',\n description:\n \"Adds a single contact to the authenticated user's personal network by email address. \" +\n \"For bulk imports, use import_contacts instead.\\n\\n\" +\n \"**What happens:** Looks up the email. If an account exists, links that user as a contact. \" +\n \"If not, creates a ghost user (placeholder enriched with public profile data) and adds them. \" +\n \"The contact can then appear in opportunity discovery within the user's personal index.\\n\\n\" +\n \"**When to use:** When the user wants to add a specific person (e.g. 'add john@example.com to my network').\\n\\n\" +\n \"**Returns:** Confirmation with the contact's userId and whether a new ghost user was created (isNewGhost). \" +\n \"Use the userId with discover_opportunities(targetUserId) to find connection opportunities.\",\n querySchema: z.object({\n email: z.string().describe('Email address of the person to add. Used as unique identifier — if already a contact, the operation is idempotent.'),\n name: z.string().optional().describe('Full name of the contact. Optional — if omitted, the email prefix is used as name. Provide when known for better profile enrichment.'),\n }),\n handler: async ({ context, query }) => {\n try {\n const result = await contactService.addContact(context.userId, query.email, { name: query.name, restore: true });\n\n return success({\n added: true,\n message: result.isNew\n ? `Added ${query.name || query.email} to your network. Their profile is being enriched.`\n : `Added ${query.name || query.email} to your network.`,\n userId: result.userId,\n isNewGhost: result.isNew,\n });\n } catch (err) {\n logger.error('Failed to add contact', { err });\n return error('Failed to add contact. Please try again.');\n }\n },\n }) : null;\n\n const remove_contact = defineTool({\n name: 'remove_contact',\n description:\n \"Removes a contact from the authenticated user's personal network. The contact relationship is deleted — \" +\n \"the person is no longer a member of the user's personal index and won't appear in personal-index-scoped discovery.\\n\\n\" +\n \"**When to use:** When the user wants to remove someone from their network (e.g. 'remove John from my contacts').\\n\\n\" +\n \"**Note:** This only removes the contact relationship. If the contact is a real user (not a ghost), \" +\n \"they still exist on the platform and may appear in shared index discovery.\\n\\n\" +\n \"**Returns:** Confirmation that the contact was removed.\",\n querySchema: z.object({\n contactUserId: z.string().describe('The userId of the contact to remove. Get this from list_contacts results.'),\n }),\n handler: async ({ context, query }) => {\n try {\n await contactService.removeContact(context.userId, query.contactUserId);\n return success({ removed: true, message: 'Contact removed from your network.' });\n } catch (err) {\n logger.error('Failed to remove contact', { err });\n return error('Failed to remove contact. Please try again.');\n }\n },\n });\n\n const search_contacts = defineTool({\n name: 'search_contacts',\n description:\n \"Searches the authenticated user's personal network by name or email (case-insensitive substring). \" +\n \"Use when the user refers to a contact by partial name or email and you need their userId for another tool \" +\n \"(e.g. read_user_profiles, discover_opportunities).\\n\\n\" +\n \"**When to use:** Before list_contacts when the network is large — returns only matching contacts, bounded by limit.\\n\\n\" +\n \"**Returns:** Array of matching contacts: userId, name, email, avatar, isGhost.\",\n querySchema: z.object({\n query: z.string().trim().min(1).describe('Free-text query matched against contact name and email (case-insensitive, substring).'),\n limit: z.number().int().positive().max(100).optional().describe('Maximum rows to return. Defaults to 25.'),\n }),\n handler: async ({ context, query }) => {\n try {\n const rows = await contactService.searchContacts(context.userId, query.query, query.limit ?? 25);\n return success({\n count: rows.length,\n contacts: rows.map(r => ({\n userId: r.contactId,\n name: r.name,\n email: r.email,\n avatar: r.avatar,\n isGhost: r.isGhost,\n })),\n });\n } catch (err) {\n logger.error('Failed to search contacts', { err });\n return error('Failed to search contacts. Please try again.');\n }\n },\n });\n\n return [\n ...(import_contacts ? [import_contacts] : []),\n ...(add_contact ? [add_contact] : []),\n list_contacts,\n remove_contact,\n search_contacts,\n ];\n}\n"]}
|
|
@@ -51,6 +51,8 @@ export declare class UserContextGenerator {
|
|
|
51
51
|
* @returns Updated context text with embedding vector
|
|
52
52
|
*/
|
|
53
53
|
generateIncremental(input: IncrementalContextInput): Promise<UserContextResult>;
|
|
54
|
+
/** Normalize an LLM message content into a plain string. */
|
|
55
|
+
private extractText;
|
|
54
56
|
/** Format the network context header for LLM prompts. */
|
|
55
57
|
private formatNetworkContext;
|
|
56
58
|
/** Describe the premise change for the incremental prompt. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.generator.d.ts","sourceRoot":"/","sources":["context/context.generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAErF,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,yEAAyE;AACzE,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAMD;;;GAGG;AACH,qBAAa,oBAAoB;IAGnB,OAAO,CAAC,kBAAkB;IAFtC,OAAO,CAAC,KAAK,CAAuC;gBAEhC,kBAAkB,EAAE,kBAAkB;IAE1D;;;;;OAKG;IACG,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"context.generator.d.ts","sourceRoot":"/","sources":["context/context.generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAErF,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,yEAAyE;AACzE,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAMD;;;GAGG;AACH,qBAAa,oBAAoB;IAGnB,OAAO,CAAC,kBAAkB;IAFtC,OAAO,CAAC,KAAK,CAAuC;gBAEhC,kBAAkB,EAAE,kBAAkB;IAE1D;;;;;OAKG;IACG,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiB5E;;;;;OAKG;IACG,mBAAmB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiBrF,4DAA4D;IAC5D,OAAO,CAAC,WAAW;IAMnB,yDAAyD;IACzD,OAAO,CAAC,oBAAoB;IAM5B,8DAA8D;IAC9D,OAAO,CAAC,cAAc;IAatB,iEAAiE;YACnD,KAAK;CAIpB"}
|
|
@@ -33,9 +33,7 @@ export class UserContextGenerator {
|
|
|
33
33
|
new SystemMessage(COLD_START_SYSTEM_PROMPT),
|
|
34
34
|
new HumanMessage(`${networkContext}\n\nPremises:\n${premiseBlock}\n\nWrite a focused context paragraph for this person in this network.`),
|
|
35
35
|
]);
|
|
36
|
-
const text =
|
|
37
|
-
? response.content
|
|
38
|
-
: String(response.content ?? '').trim();
|
|
36
|
+
const text = this.extractText(response.content);
|
|
39
37
|
const embedding = await this.embed(text);
|
|
40
38
|
return { text, embedding };
|
|
41
39
|
}
|
|
@@ -52,12 +50,16 @@ export class UserContextGenerator {
|
|
|
52
50
|
new SystemMessage(INCREMENTAL_SYSTEM_PROMPT),
|
|
53
51
|
new HumanMessage(`${networkContext}\n\nCurrent context:\n${input.currentContext}\n\nChange:\n${changeDescription}\n\nWrite the updated context paragraph.`),
|
|
54
52
|
]);
|
|
55
|
-
const text =
|
|
56
|
-
? response.content
|
|
57
|
-
: String(response.content ?? '').trim();
|
|
53
|
+
const text = this.extractText(response.content);
|
|
58
54
|
const embedding = await this.embed(text);
|
|
59
55
|
return { text, embedding };
|
|
60
56
|
}
|
|
57
|
+
/** Normalize an LLM message content into a plain string. */
|
|
58
|
+
extractText(content) {
|
|
59
|
+
return typeof content === 'string'
|
|
60
|
+
? content
|
|
61
|
+
: String(content ?? '').trim();
|
|
62
|
+
}
|
|
61
63
|
/** Format the network context header for LLM prompts. */
|
|
62
64
|
formatNetworkContext(prompt, title) {
|
|
63
65
|
return prompt
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.generator.js","sourceRoot":"/","sources":["context/context.generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AA0B9F,MAAM,wBAAwB,GAAG,uXAAuX,CAAC;AAEzZ,MAAM,yBAAyB,GAAG,qcAAqc,CAAC;AAExe;;;GAGG;AACH,MAAM,OAAO,oBAAoB;IAG/B,YAAoB,kBAAsC;QAAtC,uBAAkB,GAAlB,kBAAkB,CAAoB;QAFlD,UAAK,GAAG,WAAW,CAAC,sBAAsB,CAAC,CAAC;IAES,CAAC;IAE9D;;;;;OAKG;IACH,KAAK,CAAC,iBAAiB,CAAC,KAAuB;QAC7C,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE,MAAM,cAAc,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;QAE1F,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE;YACvD,IAAI,aAAa,CAAC,wBAAwB,CAAC;YAC3C,IAAI,YAAY,CACd,GAAG,cAAc,kBAAkB,YAAY,wEAAwE,CACxH;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"context.generator.js","sourceRoot":"/","sources":["context/context.generator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * User Context Generator\n *\n * Synthesizes network-scoped context paragraphs from user premises.\n * Two modes: cold-start (all premises at once) and incremental\n * (single premise change applied to an existing context).\n * Returns { text, embedding } for storage in the user_contexts table.\n */\n\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\n\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { getAbortSignalConfig, invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport type { EmbeddingGenerator } from \"../shared/interfaces/embedder.interface.js\";\n\n/** Input for cold-start context generation from a full set of premises. */\nexport interface UserContextInput {\n premises: Array<{ text: string }>;\n networkPrompt: string | null;\n networkTitle: string;\n}\n\n/** Input for incremental context update from a single premise change. */\nexport interface IncrementalContextInput {\n currentContext: string;\n changeType: 'added' | 'updated' | 'retracted' | 'expired';\n premiseText: string;\n previousPremiseText?: string;\n networkPrompt: string | null;\n networkTitle: string;\n}\n\n/** Generated context paragraph with its embedding vector. */\nexport interface UserContextResult {\n text: string;\n embedding: number[];\n}\n\nconst COLD_START_SYSTEM_PROMPT = `You synthesize user context paragraphs for community matching. Given a list of premises (atomic facts about a person) and a network description, write a focused paragraph (3-6 sentences) that captures who this person is through the lens of that network's purpose. Highlight what is most relevant to the network. Write in third person. Be specific and concrete, not generic.`;\n\nconst INCREMENTAL_SYSTEM_PROMPT = `You maintain user context paragraphs for community matching. You will receive the current context paragraph, a change that occurred, and the network description. Update the context paragraph to reflect the change while preserving all other information. Keep the same style: 3-6 sentences, third person, specific and concrete. For retractions/expirations, remove the relevant information. For additions/updates, integrate the new information naturally.`;\n\n/**\n * Generates network-scoped context paragraphs from user premises.\n * Uses LLM synthesis to produce focused context and an embedding vector.\n */\nexport class UserContextGenerator {\n private model = createModel('userContextGenerator');\n\n constructor(private embeddingGenerator: EmbeddingGenerator) {}\n\n /**\n * Generate a context paragraph from the full set of premises (cold-start).\n *\n * @param input - Premises, network prompt, and network title\n * @returns Synthesized context text with embedding vector\n */\n async generateColdStart(input: UserContextInput): Promise<UserContextResult> {\n const premiseBlock = input.premises.map(p => `- ${p.text}`).join('\\n');\n const networkContext = this.formatNetworkContext(input.networkPrompt, input.networkTitle);\n\n const response = await invokeWithAbortSignal(this.model, [\n new SystemMessage(COLD_START_SYSTEM_PROMPT),\n new HumanMessage(\n `${networkContext}\\n\\nPremises:\\n${premiseBlock}\\n\\nWrite a focused context paragraph for this person in this network.`,\n ),\n ]);\n\n const text = this.extractText(response.content);\n\n const embedding = await this.embed(text);\n return { text, embedding };\n }\n\n /**\n * Update an existing context paragraph after a single premise change.\n *\n * @param input - Current context, change details, network metadata\n * @returns Updated context text with embedding vector\n */\n async generateIncremental(input: IncrementalContextInput): Promise<UserContextResult> {\n const networkContext = this.formatNetworkContext(input.networkPrompt, input.networkTitle);\n const changeDescription = this.describeChange(input);\n\n const response = await invokeWithAbortSignal(this.model, [\n new SystemMessage(INCREMENTAL_SYSTEM_PROMPT),\n new HumanMessage(\n `${networkContext}\\n\\nCurrent context:\\n${input.currentContext}\\n\\nChange:\\n${changeDescription}\\n\\nWrite the updated context paragraph.`,\n ),\n ]);\n\n const text = this.extractText(response.content);\n\n const embedding = await this.embed(text);\n return { text, embedding };\n }\n\n /** Normalize an LLM message content into a plain string. */\n private extractText(content: unknown): string {\n return typeof content === 'string'\n ? content\n : String(content ?? '').trim();\n }\n\n /** Format the network context header for LLM prompts. */\n private formatNetworkContext(prompt: string | null, title: string): string {\n return prompt\n ? `Network \"${title}\": ${prompt}`\n : `Network: ${title}`;\n }\n\n /** Describe the premise change for the incremental prompt. */\n private describeChange(input: IncrementalContextInput): string {\n switch (input.changeType) {\n case 'added':\n return `A new fact was learned about this person:\\n\"${input.premiseText}\"`;\n case 'updated':\n return `A fact was updated.\\nOld: \"${input.previousPremiseText}\"\\nNew: \"${input.premiseText}\"`;\n case 'retracted':\n return `A fact was retracted (no longer true):\\n\"${input.premiseText}\"`;\n case 'expired':\n return `A fact has expired (time-bound, no longer current):\\n\"${input.premiseText}\"`;\n }\n }\n\n /** Generate embedding from text, normalizing the return type. */\n private async embed(text: string): Promise<number[]> {\n const result = await this.embeddingGenerator.generate(text, undefined, getAbortSignalConfig());\n return Array.isArray(result[0]) ? result[0] as number[] : result as number[];\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.tools.d.ts","sourceRoot":"/","sources":["integration/integration.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAO5E;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"integration.tools.d.ts","sourceRoot":"/","sources":["integration/integration.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAO5E;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,SA6E5E"}
|
|
@@ -15,6 +15,12 @@ const logger = protocolLogger('IntegrationTools');
|
|
|
15
15
|
*/
|
|
16
16
|
export function createIntegrationTools(defineTool, deps) {
|
|
17
17
|
const { integration, integrationImporter } = deps;
|
|
18
|
+
// import_gmail_contacts creates ghost users, so it is gated behind the
|
|
19
|
+
// CONTACTS_ENABLED flag (injected as deps.contactsEnabled). When disabled the
|
|
20
|
+
// tool is not registered at all (defineTool registers as a side effect).
|
|
21
|
+
if (deps.contactsEnabled !== true) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
18
24
|
const import_gmail_contacts = defineTool({
|
|
19
25
|
name: 'import_gmail_contacts',
|
|
20
26
|
description: "Imports contacts from the user's connected Gmail/Google account into their personal network. " +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.tools.js","sourceRoot":"/","sources":["integration/integration.tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAE5E,MAAM,MAAM,GAAG,cAAc,CAAC,kBAAkB,CAAC,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAsB,EAAE,IAAc;IAC3E,MAAM,EAAE,WAAW,EAAE,mBAAmB,EAAE,GAAG,IAAI,CAAC;IAElD,MAAM,qBAAqB,GAAG,UAAU,CAAC;QACvC,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EACT,+FAA+F;YAC/F,6IAA6I;YAC7I,oHAAoH;YACpH,mFAAmF;YACnF,qFAAqF;YACrF,kIAAkI;YAClI,6FAA6F;YAC7F,8GAA8G;YAC9G,2GAA2G;YAC3G,wHAAwH;QAC1H,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAC7B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAE1C,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;gBAClE,MAAM,WAAW,GAAG,CAAC,CAAC,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,CAAC;gBAErE,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,MAAM,CAAC,IAAI,CAAC,yCAAyC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;oBACnF,MAAM,SAAS,GAAG,cAAc,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC;oBACvD,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;oBAE1E,MAAM,WAAW,GAAG,MAAO,OAAO,CAAC,SAAoC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;oBAC5H,OAAO,OAAO,CAAC;wBACb,YAAY,EAAE,IAAI;wBAClB,OAAO,EAAE,uDAAuD;wBAChE,OAAO,EAAE,WAAW,CAAC,WAAW;qBACjC,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEvF,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE;oBACrC,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,QAAQ,EAAE,YAAY,CAAC,QAAQ;oBAC/B,OAAO,EAAE,YAAY,CAAC,OAAO;oBAC7B,WAAW,EAAE,YAAY,CAAC,WAAW;oBACrC,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;iBAChD,CAAC,CAAC;gBAEH,OAAO,OAAO,CAAC;oBACb,OAAO,EAAE,YAAY,CAAC,WAAW,GAAG,CAAC;wBACnC,CAAC,CAAC,YAAY,YAAY,CAAC,QAAQ,yBAAyB,YAAY,CAAC,WAAW,SAAS,YAAY,CAAC,gBAAgB,2BAA2B;wBACrJ,CAAC,CAAC,YAAY,CAAC,QAAQ,GAAG,CAAC;4BACzB,CAAC,CAAC,OAAO,YAAY,CAAC,QAAQ,2EAA2E;4BACzG,CAAC,CAAC,oEAAoE;oBAC1E,QAAQ,EAAE,YAAY,CAAC,QAAQ;oBAC/B,WAAW,EAAE,YAAY,CAAC,WAAW;oBACrC,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;oBAC/C,OAAO,EAAE,YAAY,CAAC,OAAO;iBAC9B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE;oBAC3C,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,GAAG;iBACJ,CAAC,CAAC;gBACH,OAAO,KAAK,CAAC,oDAAoD,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CAAC,qBAAqB,CAAC,CAAC;AACjC,CAAC","sourcesContent":["import { z } from 'zod';\nimport type { DefineTool, ToolDeps } from '../shared/agent/tool.helpers.js';\nimport { success, error } from '../shared/agent/tool.helpers.js';\nimport { requestContext } from \"../shared/observability/request-context.js\";\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('IntegrationTools');\n\n/**\n * Creates integration tools for the chat agent.\n *\n * Exposes `import_gmail_contacts` which authenticates via the integration adapter,\n * fetches all Gmail contacts (paginated), and imports them as ghost users into the network.\n *\n * @param defineTool - Tool definition helper injected by the tool registry.\n * @param deps - Shared tool dependencies including the integration adapter.\n * @returns An array of tool definitions to register with the chat agent.\n */\nexport function createIntegrationTools(defineTool: DefineTool, deps: ToolDeps) {\n const { integration, integrationImporter } = deps;\n\n const import_gmail_contacts = defineTool({\n name: 'import_gmail_contacts',\n description:\n \"Imports contacts from the user's connected Gmail/Google account into their personal network. \" +\n \"This is the preferred method for importing Google Contacts — handles OAuth authentication, pagination, and deduplication automatically.\\n\\n\" +\n \"**Authentication flow:** If Gmail is not yet connected, returns an `authUrl` the user must visit to grant access. \" +\n \"After they complete OAuth, call this tool again to perform the actual import.\\n\\n\" +\n \"**What happens on import:** All Gmail contacts with valid name+email are imported. \" +\n \"Contacts without existing platform accounts become ghost users (enriched with public profile data from LinkedIn, GitHub, etc.). \" +\n \"All imported contacts are added to the user's personal index for opportunity discovery.\\n\\n\" +\n \"**When to use:** When the user asks to import or sync their Gmail/Google contacts. No parameters needed.\\n\\n\" +\n \"**Returns:** Either `{ requiresAuth: true, authUrl }` (user needs to authenticate) or import statistics: \" +\n \"imported (total), newContacts (ghost users created), existingContacts (already in network), skipped (invalid entries).\",\n querySchema: z.object({}),\n handler: async ({ context }) => {\n try {\n const session = await integration.createSession(context.userId);\n const toolkits = await session.toolkits();\n\n const gmailToolkit = toolkits.items.find(t => t.slug === 'gmail');\n const isConnected = !!gmailToolkit?.connection?.connectedAccount?.id;\n\n if (!isConnected) {\n logger.info('Gmail not connected, returning auth URL', { userId: context.userId });\n const originUrl = requestContext.getStore()?.originUrl;\n const callbackUrl = originUrl ? `${originUrl}/oauth/callback` : undefined;\n type AuthorizeFn = (toolkit: string, options?: { callbackUrl?: string }) => Promise<{ redirectUrl: string }>;\n const authRequest = await (session.authorize as unknown as AuthorizeFn)('gmail', callbackUrl ? { callbackUrl } : undefined);\n return success({\n requiresAuth: true,\n message: 'Please connect your Gmail account to import contacts.',\n authUrl: authRequest.redirectUrl,\n });\n }\n\n const importResult = await integrationImporter.importContacts(context.userId, 'gmail');\n\n logger.info('Gmail contacts imported', {\n userId: context.userId,\n imported: importResult.imported,\n skipped: importResult.skipped,\n newContacts: importResult.newContacts,\n existingContacts: importResult.existingContacts,\n });\n\n return success({\n message: importResult.newContacts > 0\n ? `Imported ${importResult.imported} contacts from Gmail. ${importResult.newContacts} new, ${importResult.existingContacts} already in your network.`\n : importResult.imported > 0\n ? `All ${importResult.imported} contacts from Gmail were already in your network. No new contacts added.`\n : 'No contacts with valid name and email found in your Gmail account.',\n imported: importResult.imported,\n newContacts: importResult.newContacts,\n existingContacts: importResult.existingContacts,\n skipped: importResult.skipped,\n });\n } catch (err) {\n logger.error('import_gmail_contacts failed', {\n userId: context.userId,\n err,\n });\n return error('Failed to import Gmail contacts. Please try again.');\n }\n },\n });\n\n return [import_gmail_contacts];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"integration.tools.js","sourceRoot":"/","sources":["integration/integration.tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { DefineTool, ToolDeps } from '../shared/agent/tool.helpers.js';\nimport { success, error } from '../shared/agent/tool.helpers.js';\nimport { requestContext } from \"../shared/observability/request-context.js\";\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('IntegrationTools');\n\n/**\n * Creates integration tools for the chat agent.\n *\n * Exposes `import_gmail_contacts` which authenticates via the integration adapter,\n * fetches all Gmail contacts (paginated), and imports them as ghost users into the network.\n *\n * @param defineTool - Tool definition helper injected by the tool registry.\n * @param deps - Shared tool dependencies including the integration adapter.\n * @returns An array of tool definitions to register with the chat agent.\n */\nexport function createIntegrationTools(defineTool: DefineTool, deps: ToolDeps) {\n const { integration, integrationImporter } = deps;\n\n // import_gmail_contacts creates ghost users, so it is gated behind the\n // CONTACTS_ENABLED flag (injected as deps.contactsEnabled). When disabled the\n // tool is not registered at all (defineTool registers as a side effect).\n if (deps.contactsEnabled !== true) {\n return [];\n }\n\n const import_gmail_contacts = defineTool({\n name: 'import_gmail_contacts',\n description:\n \"Imports contacts from the user's connected Gmail/Google account into their personal network. \" +\n \"This is the preferred method for importing Google Contacts — handles OAuth authentication, pagination, and deduplication automatically.\\n\\n\" +\n \"**Authentication flow:** If Gmail is not yet connected, returns an `authUrl` the user must visit to grant access. \" +\n \"After they complete OAuth, call this tool again to perform the actual import.\\n\\n\" +\n \"**What happens on import:** All Gmail contacts with valid name+email are imported. \" +\n \"Contacts without existing platform accounts become ghost users (enriched with public profile data from LinkedIn, GitHub, etc.). \" +\n \"All imported contacts are added to the user's personal index for opportunity discovery.\\n\\n\" +\n \"**When to use:** When the user asks to import or sync their Gmail/Google contacts. No parameters needed.\\n\\n\" +\n \"**Returns:** Either `{ requiresAuth: true, authUrl }` (user needs to authenticate) or import statistics: \" +\n \"imported (total), newContacts (ghost users created), existingContacts (already in network), skipped (invalid entries).\",\n querySchema: z.object({}),\n handler: async ({ context }) => {\n try {\n const session = await integration.createSession(context.userId);\n const toolkits = await session.toolkits();\n\n const gmailToolkit = toolkits.items.find(t => t.slug === 'gmail');\n const isConnected = !!gmailToolkit?.connection?.connectedAccount?.id;\n\n if (!isConnected) {\n logger.info('Gmail not connected, returning auth URL', { userId: context.userId });\n const originUrl = requestContext.getStore()?.originUrl;\n const callbackUrl = originUrl ? `${originUrl}/oauth/callback` : undefined;\n type AuthorizeFn = (toolkit: string, options?: { callbackUrl?: string }) => Promise<{ redirectUrl: string }>;\n const authRequest = await (session.authorize as unknown as AuthorizeFn)('gmail', callbackUrl ? { callbackUrl } : undefined);\n return success({\n requiresAuth: true,\n message: 'Please connect your Gmail account to import contacts.',\n authUrl: authRequest.redirectUrl,\n });\n }\n\n const importResult = await integrationImporter.importContacts(context.userId, 'gmail');\n\n logger.info('Gmail contacts imported', {\n userId: context.userId,\n imported: importResult.imported,\n skipped: importResult.skipped,\n newContacts: importResult.newContacts,\n existingContacts: importResult.existingContacts,\n });\n\n return success({\n message: importResult.newContacts > 0\n ? `Imported ${importResult.imported} contacts from Gmail. ${importResult.newContacts} new, ${importResult.existingContacts} already in your network.`\n : importResult.imported > 0\n ? `All ${importResult.imported} contacts from Gmail were already in your network. No new contacts added.`\n : 'No contacts with valid name and email found in your Gmail account.',\n imported: importResult.imported,\n newContacts: importResult.newContacts,\n existingContacts: importResult.existingContacts,\n skipped: importResult.skipped,\n });\n } catch (err) {\n logger.error('import_gmail_contacts failed', {\n userId: context.userId,\n err,\n });\n return error('Failed to import Gmail contacts. Please try again.');\n }\n },\n });\n\n return [import_gmail_contacts];\n}\n"]}
|
|
@@ -21,6 +21,8 @@ export declare class IntentClarifier {
|
|
|
21
21
|
private readonly suggestionModel;
|
|
22
22
|
private readonly clarificationDraftModel;
|
|
23
23
|
constructor();
|
|
24
|
+
/** Build the shared user prompt with intent, profile, and active-intent context. */
|
|
25
|
+
private buildPrompt;
|
|
24
26
|
invoke(description: string, profileContext: string, activeIntentsContext: string): Promise<IntentClarifierOutput>;
|
|
25
27
|
private generateSuggestion;
|
|
26
28
|
private generateClarificationDraft;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent.clarifier.d.ts","sourceRoot":"/","sources":["intent/intent.clarifier.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;EAKvB,CAAC;AASH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AA4CxE,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2B;IAC3D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA2B;;
|
|
1
|
+
{"version":3,"file":"intent.clarifier.d.ts","sourceRoot":"/","sources":["intent/intent.clarifier.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;EAKvB,CAAC;AASH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AA4CxE,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2B;IAC3D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA2B;;IAgBnE,oFAAoF;IACpF,OAAO,CAAC,WAAW;IAkBN,MAAM,CACjB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CAAC,qBAAqB,CAAC;YAkCnB,kBAAkB;YAoBlB,0BAA0B;CA2BzC"}
|
|
@@ -77,9 +77,9 @@ export class IntentClarifier {
|
|
|
77
77
|
});
|
|
78
78
|
this.clarificationDraftModel = baseModel.withStructuredOutput(clarificationDraftSchema, { name: "intent_clarifier_message" });
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
/** Build the shared user prompt with intent, profile, and active-intent context. */
|
|
81
|
+
buildPrompt(description, profileContext, activeIntentsContext) {
|
|
82
|
+
return `
|
|
83
83
|
# User Input Intent
|
|
84
84
|
${description}
|
|
85
85
|
|
|
@@ -89,6 +89,10 @@ ${profileContext || "none"}
|
|
|
89
89
|
# Active Intents
|
|
90
90
|
${activeIntentsContext || "none"}
|
|
91
91
|
`;
|
|
92
|
+
}
|
|
93
|
+
async invoke(description, profileContext, activeIntentsContext) {
|
|
94
|
+
try {
|
|
95
|
+
const prompt = this.buildPrompt(description, profileContext, activeIntentsContext);
|
|
92
96
|
const result = await invokeWithAbortSignal(this.model, [
|
|
93
97
|
new SystemMessage(systemPrompt),
|
|
94
98
|
new HumanMessage(prompt),
|
|
@@ -119,16 +123,7 @@ ${activeIntentsContext || "none"}
|
|
|
119
123
|
}
|
|
120
124
|
async generateSuggestion(description, profileContext, activeIntentsContext) {
|
|
121
125
|
try {
|
|
122
|
-
const prompt =
|
|
123
|
-
# User Input Intent
|
|
124
|
-
${description}
|
|
125
|
-
|
|
126
|
-
# User Profile
|
|
127
|
-
${profileContext || "none"}
|
|
128
|
-
|
|
129
|
-
# Active Intents
|
|
130
|
-
${activeIntentsContext || "none"}
|
|
131
|
-
`;
|
|
126
|
+
const prompt = this.buildPrompt(description, profileContext, activeIntentsContext);
|
|
132
127
|
const output = await invokeWithAbortSignal(this.suggestionModel, [
|
|
133
128
|
new SystemMessage(suggestionPrompt),
|
|
134
129
|
new HumanMessage(prompt),
|
|
@@ -144,16 +139,7 @@ ${activeIntentsContext || "none"}
|
|
|
144
139
|
}
|
|
145
140
|
async generateClarificationDraft(description, profileContext, activeIntentsContext) {
|
|
146
141
|
try {
|
|
147
|
-
const prompt =
|
|
148
|
-
# User Input Intent
|
|
149
|
-
${description}
|
|
150
|
-
|
|
151
|
-
# User Profile
|
|
152
|
-
${profileContext || "none"}
|
|
153
|
-
|
|
154
|
-
# Active Intents
|
|
155
|
-
${activeIntentsContext || "none"}
|
|
156
|
-
`;
|
|
142
|
+
const prompt = this.buildPrompt(description, profileContext, activeIntentsContext);
|
|
157
143
|
const output = await invokeWithAbortSignal(this.clarificationDraftModel, [
|
|
158
144
|
new SystemMessage(clarificationDraftPrompt),
|
|
159
145
|
new HumanMessage(prompt),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent.clarifier.js","sourceRoot":"/","sources":["intent/intent.clarifier.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\n\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\n\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"IntentClarifier\");\n\ntype ClarifierStructuredModel = ReturnType<ChatOpenAI[\"withStructuredOutput\"]>;\n\nconst clarificationSchema = z.object({\n needsClarification: z.boolean(),\n reason: z.string(),\n suggestedDescription: z.string().nullable(),\n clarificationMessage: z.string().nullable(),\n});\nconst suggestionSchema = z.object({\n suggestedDescription: z.string(),\n});\nconst clarificationDraftSchema = z.object({\n suggestedDescription: z.string(),\n clarificationMessage: z.string(),\n});\n\nexport type IntentClarifierOutput = z.infer<typeof clarificationSchema>;\n\nconst systemPrompt = `\nYou evaluate whether an intent is specific enough to persist without asking the user to confirm a refinement.\n\nOnly set needsClarification=true when the intent is truly vague — e.g. a single generic phrase with no role, domain, location, or other concrete criteria (like \"find a job\", \"I need help\", \"looking for something\").\n\nDo NOT ask for clarification when the user has already given:\n- A role or type (e.g. \"UX designer\", \"technical co-founder\", \"engineer\")\n- A domain or industry (e.g. \"in AI\", \"climate tech\", \"fintech\")\n- A location or format (e.g. \"remote\", \"Berlin\", \"full-time\")\n- Any other concrete detail that makes the intent actionable\n\nDefault to needsClarification=false when in doubt. Only clarify when the intent is so broad that persisting it as-is would be unhelpful (e.g. literally \"a job\" or \"something\" with no other signal).\n\nRules when needsClarification=true:\n- User Profile is the primary source for suggestedDescription; Active Intents are secondary.\n- You MUST provide a concrete suggestedDescription and short clarificationMessage.\n- Do not include JSON in clarificationMessage.\n`;\n\nconst suggestionPrompt = `\nYou generate one concrete, specific intent rewrite.\n\nRules:\n- Output only a concise intent sentence in suggestedDescription.\n- Use profile as primary source of personalization.\n- Use active intents as secondary context for consistency.\n- Keep user intent meaning, but make it actionable and specific.\n- Never return an empty suggestion.\n`;\n\nconst clarificationDraftPrompt = `\nYou draft a concise clarification response for a vague intent.\n\nRules:\n- Return both:\n 1) suggestedDescription (specific rewritten intent)\n 2) clarificationMessage (single short message to the user)\n- clarificationMessage must include the suggestion naturally and ask for confirmation.\n- Use this shape: ` + \"`Did you mean: \\\"<suggestedDescription>\\\"?`\" + ` followed by a brief confirmation instruction.\n- Keep it short. No bullet lists. No JSON.\n`;\n\nexport class IntentClarifier {\n private readonly model: ClarifierStructuredModel;\n private readonly suggestionModel: ClarifierStructuredModel;\n private readonly clarificationDraftModel: ClarifierStructuredModel;\n\n constructor() {\n const baseModel = createModel(\"intentClarifier\");\n this.model = baseModel.withStructuredOutput(clarificationSchema, {\n name: \"intent_clarifier\",\n });\n this.suggestionModel = baseModel.withStructuredOutput(suggestionSchema, {\n name: \"intent_clarifier_suggestion\",\n });\n this.clarificationDraftModel = baseModel.withStructuredOutput(\n clarificationDraftSchema,\n { name: \"intent_clarifier_message\" }\n );\n }\n\n @Timed()\n public async invoke(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): Promise<IntentClarifierOutput> {\n try {\n const prompt = `\n# User Input Intent\n${description}\n\n# User Profile\n${profileContext || \"none\"}\n\n# Active Intents\n${activeIntentsContext || \"none\"}\n`;\n\n const result = await invokeWithAbortSignal(this.model, [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ]);\n const parsed = clarificationSchema.parse(result);\n\n if (parsed.needsClarification) {\n // Always prefer a dedicated rewrite pass for vague inputs so we avoid generic follow-up text.\n const draft = await this.generateClarificationDraft(description, profileContext, activeIntentsContext);\n if (draft) {\n return {\n ...parsed,\n suggestedDescription: draft.suggestedDescription,\n clarificationMessage: draft.clarificationMessage,\n };\n }\n }\n\n return parsed;\n } catch (error) {\n logger.warn(\"invoke: clarification failed\", { error });\n return {\n needsClarification: false,\n reason: \"fallback_on_model_error\",\n suggestedDescription: null,\n clarificationMessage: null,\n };\n }\n }\n\n private async generateSuggestion(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): Promise<string | null> {\n try {\n const prompt = `\n# User Input Intent\n${description}\n\n# User Profile\n${profileContext || \"none\"}\n\n# Active Intents\n${activeIntentsContext || \"none\"}\n`;\n const output = await invokeWithAbortSignal(this.suggestionModel, [\n new SystemMessage(suggestionPrompt),\n new HumanMessage(prompt),\n ]);\n const parsed = suggestionSchema.parse(output);\n const suggestion = parsed.suggestedDescription.trim();\n return suggestion.length > 0 ? suggestion : null;\n } catch (error) {\n logger.warn(\"generateSuggestion: failed\", { error });\n return null;\n }\n }\n\n private async generateClarificationDraft(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): Promise<{ suggestedDescription: string; clarificationMessage: string } | null> {\n try {\n const prompt = `\n# User Input Intent\n${description}\n\n# User Profile\n${profileContext || \"none\"}\n\n# Active Intents\n${activeIntentsContext || \"none\"}\n`;\n const output = await invokeWithAbortSignal(this.clarificationDraftModel, [\n new SystemMessage(clarificationDraftPrompt),\n new HumanMessage(prompt),\n ]);\n const parsed = clarificationDraftSchema.parse(output);\n const suggestedDescription = parsed.suggestedDescription.trim();\n const clarificationMessage = parsed.clarificationMessage.trim();\n if (!suggestedDescription || !clarificationMessage) return null;\n return { suggestedDescription, clarificationMessage };\n } catch (error) {\n logger.warn(\"generateClarificationDraft: failed\", { error });\n const suggestion = await this.generateSuggestion(description, profileContext, activeIntentsContext);\n if (!suggestion) return null;\n const clarificationMessage = `Do you mean: ${suggestion}?`;\n return {\n suggestedDescription: suggestion,\n clarificationMessage,\n };\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"intent.clarifier.js","sourceRoot":"/","sources":["intent/intent.clarifier.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod\";\n\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\n\nimport { createModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\n\nconst logger = protocolLogger(\"IntentClarifier\");\n\ntype ClarifierStructuredModel = ReturnType<ChatOpenAI[\"withStructuredOutput\"]>;\n\nconst clarificationSchema = z.object({\n needsClarification: z.boolean(),\n reason: z.string(),\n suggestedDescription: z.string().nullable(),\n clarificationMessage: z.string().nullable(),\n});\nconst suggestionSchema = z.object({\n suggestedDescription: z.string(),\n});\nconst clarificationDraftSchema = z.object({\n suggestedDescription: z.string(),\n clarificationMessage: z.string(),\n});\n\nexport type IntentClarifierOutput = z.infer<typeof clarificationSchema>;\n\nconst systemPrompt = `\nYou evaluate whether an intent is specific enough to persist without asking the user to confirm a refinement.\n\nOnly set needsClarification=true when the intent is truly vague — e.g. a single generic phrase with no role, domain, location, or other concrete criteria (like \"find a job\", \"I need help\", \"looking for something\").\n\nDo NOT ask for clarification when the user has already given:\n- A role or type (e.g. \"UX designer\", \"technical co-founder\", \"engineer\")\n- A domain or industry (e.g. \"in AI\", \"climate tech\", \"fintech\")\n- A location or format (e.g. \"remote\", \"Berlin\", \"full-time\")\n- Any other concrete detail that makes the intent actionable\n\nDefault to needsClarification=false when in doubt. Only clarify when the intent is so broad that persisting it as-is would be unhelpful (e.g. literally \"a job\" or \"something\" with no other signal).\n\nRules when needsClarification=true:\n- User Profile is the primary source for suggestedDescription; Active Intents are secondary.\n- You MUST provide a concrete suggestedDescription and short clarificationMessage.\n- Do not include JSON in clarificationMessage.\n`;\n\nconst suggestionPrompt = `\nYou generate one concrete, specific intent rewrite.\n\nRules:\n- Output only a concise intent sentence in suggestedDescription.\n- Use profile as primary source of personalization.\n- Use active intents as secondary context for consistency.\n- Keep user intent meaning, but make it actionable and specific.\n- Never return an empty suggestion.\n`;\n\nconst clarificationDraftPrompt = `\nYou draft a concise clarification response for a vague intent.\n\nRules:\n- Return both:\n 1) suggestedDescription (specific rewritten intent)\n 2) clarificationMessage (single short message to the user)\n- clarificationMessage must include the suggestion naturally and ask for confirmation.\n- Use this shape: ` + \"`Did you mean: \\\"<suggestedDescription>\\\"?`\" + ` followed by a brief confirmation instruction.\n- Keep it short. No bullet lists. No JSON.\n`;\n\nexport class IntentClarifier {\n private readonly model: ClarifierStructuredModel;\n private readonly suggestionModel: ClarifierStructuredModel;\n private readonly clarificationDraftModel: ClarifierStructuredModel;\n\n constructor() {\n const baseModel = createModel(\"intentClarifier\");\n this.model = baseModel.withStructuredOutput(clarificationSchema, {\n name: \"intent_clarifier\",\n });\n this.suggestionModel = baseModel.withStructuredOutput(suggestionSchema, {\n name: \"intent_clarifier_suggestion\",\n });\n this.clarificationDraftModel = baseModel.withStructuredOutput(\n clarificationDraftSchema,\n { name: \"intent_clarifier_message\" }\n );\n }\n\n /** Build the shared user prompt with intent, profile, and active-intent context. */\n private buildPrompt(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): string {\n return `\n# User Input Intent\n${description}\n\n# User Profile\n${profileContext || \"none\"}\n\n# Active Intents\n${activeIntentsContext || \"none\"}\n`;\n }\n\n @Timed()\n public async invoke(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): Promise<IntentClarifierOutput> {\n try {\n const prompt = this.buildPrompt(description, profileContext, activeIntentsContext);\n\n const result = await invokeWithAbortSignal(this.model, [\n new SystemMessage(systemPrompt),\n new HumanMessage(prompt),\n ]);\n const parsed = clarificationSchema.parse(result);\n\n if (parsed.needsClarification) {\n // Always prefer a dedicated rewrite pass for vague inputs so we avoid generic follow-up text.\n const draft = await this.generateClarificationDraft(description, profileContext, activeIntentsContext);\n if (draft) {\n return {\n ...parsed,\n suggestedDescription: draft.suggestedDescription,\n clarificationMessage: draft.clarificationMessage,\n };\n }\n }\n\n return parsed;\n } catch (error) {\n logger.warn(\"invoke: clarification failed\", { error });\n return {\n needsClarification: false,\n reason: \"fallback_on_model_error\",\n suggestedDescription: null,\n clarificationMessage: null,\n };\n }\n }\n\n private async generateSuggestion(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): Promise<string | null> {\n try {\n const prompt = this.buildPrompt(description, profileContext, activeIntentsContext);\n const output = await invokeWithAbortSignal(this.suggestionModel, [\n new SystemMessage(suggestionPrompt),\n new HumanMessage(prompt),\n ]);\n const parsed = suggestionSchema.parse(output);\n const suggestion = parsed.suggestedDescription.trim();\n return suggestion.length > 0 ? suggestion : null;\n } catch (error) {\n logger.warn(\"generateSuggestion: failed\", { error });\n return null;\n }\n }\n\n private async generateClarificationDraft(\n description: string,\n profileContext: string,\n activeIntentsContext: string\n ): Promise<{ suggestedDescription: string; clarificationMessage: string } | null> {\n try {\n const prompt = this.buildPrompt(description, profileContext, activeIntentsContext);\n const output = await invokeWithAbortSignal(this.clarificationDraftModel, [\n new SystemMessage(clarificationDraftPrompt),\n new HumanMessage(prompt),\n ]);\n const parsed = clarificationDraftSchema.parse(output);\n const suggestedDescription = parsed.suggestedDescription.trim();\n const clarificationMessage = parsed.clarificationMessage.trim();\n if (!suggestedDescription || !clarificationMessage) return null;\n return { suggestedDescription, clarificationMessage };\n } catch (error) {\n logger.warn(\"generateClarificationDraft: failed\", { error });\n const suggestion = await this.generateSuggestion(description, profileContext, activeIntentsContext);\n if (!suggestion) return null;\n const clarificationMessage = `Do you mean: ${suggestion}?`;\n return {\n suggestedDescription: suggestion,\n clarificationMessage,\n };\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent.graph.d.ts","sourceRoot":"/","sources":["intent/intent.graph.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAEjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAIhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AA0G7E;;GAEG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ,CAAC;IACjB,OAAO,CAAC,WAAW,CAAC;IACpB,OAAO,CAAC,iBAAiB,CAAC;gBAHlB,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,CAAC,EAAE,kBAAkB,YAAA,EAC7B,WAAW,CAAC,EAAE,gBAAgB,YAAA,EAC9B,iBAAiB,CAAC,EAAE,mBAAmB,YAAA;IAG1C,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAiDZ,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAFI,CAAR;4BAEI,CAAA;;;;;;;;;;;0BAFI,CAAR;4BAEI,CAAA;;;;;;;;;;;0BAFI,CAAR;4BAEI,CAAA;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"intent.graph.d.ts","sourceRoot":"/","sources":["intent/intent.graph.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAEjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAIhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AA0G7E;;GAEG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ,CAAC;IACjB,OAAO,CAAC,WAAW,CAAC;IACpB,OAAO,CAAC,iBAAiB,CAAC;gBAHlB,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,CAAC,EAAE,kBAAkB,YAAA,EAC7B,WAAW,CAAC,EAAE,gBAAgB,YAAA,EAC9B,iBAAiB,CAAC,EAAE,mBAAmB,YAAA;IAG1C,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAiDZ,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;sBAFI,CAAR;wBAEI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAFI,CAAR;4BAEI,CAAA;;;;;;;;;;;0BAFI,CAAR;4BAEI,CAAA;;;;;;;;;;;0BAFI,CAAR;4BAEI,CAAA;;;;;;;;;;CAwzBH"}
|
|
@@ -441,6 +441,33 @@ export class IntentGraphFactory {
|
|
|
441
441
|
.trim();
|
|
442
442
|
return out.replace(/[.,;]\s*$/, "").trim() || payload;
|
|
443
443
|
};
|
|
444
|
+
/**
|
|
445
|
+
* Generate a flat embedding for an intent payload, swallowing failures so
|
|
446
|
+
* persistence can continue without an embedding. `intentId` is logging-only
|
|
447
|
+
* (present for updates, absent for creates).
|
|
448
|
+
*/
|
|
449
|
+
const generateIntentEmbedding = async (sanitizedPayload, intentId) => {
|
|
450
|
+
if (!this.embedder)
|
|
451
|
+
return undefined;
|
|
452
|
+
try {
|
|
453
|
+
const embedding = await this.embedder.generate(sanitizedPayload, undefined, getAbortSignalConfig());
|
|
454
|
+
const flatEmbedding = Array.isArray(embedding?.[0])
|
|
455
|
+
? embedding[0]
|
|
456
|
+
: embedding;
|
|
457
|
+
logger.verbose("Generated embedding for intent", {
|
|
458
|
+
...(intentId ? { intentId } : {}),
|
|
459
|
+
dimensions: flatEmbedding?.length,
|
|
460
|
+
});
|
|
461
|
+
return flatEmbedding;
|
|
462
|
+
}
|
|
463
|
+
catch (embErr) {
|
|
464
|
+
logger.error("Failed to generate embedding for intent (continuing without)", {
|
|
465
|
+
...(intentId ? { intentId } : {}),
|
|
466
|
+
error: embErr,
|
|
467
|
+
});
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
444
471
|
/**
|
|
445
472
|
* Node 4: Executor
|
|
446
473
|
* Executes reconciler actions against the database.
|
|
@@ -467,19 +494,7 @@ export class IntentGraphFactory {
|
|
|
467
494
|
const matchedVerifiedIntent = verifiedIntentByPayload.get(createAction.payload) ||
|
|
468
495
|
verifiedIntentByPayload.get(sanitizedPayload);
|
|
469
496
|
// Generate embedding for the intent payload
|
|
470
|
-
|
|
471
|
-
if (this.embedder) {
|
|
472
|
-
try {
|
|
473
|
-
const embedding = await this.embedder.generate(sanitizedPayload, undefined, getAbortSignalConfig());
|
|
474
|
-
flatEmbedding = Array.isArray(embedding?.[0])
|
|
475
|
-
? embedding[0]
|
|
476
|
-
: embedding;
|
|
477
|
-
logger.verbose("Generated embedding for new intent", { dimensions: flatEmbedding?.length });
|
|
478
|
-
}
|
|
479
|
-
catch (embErr) {
|
|
480
|
-
logger.error("Failed to generate embedding for intent (continuing without)", { error: embErr });
|
|
481
|
-
}
|
|
482
|
-
}
|
|
497
|
+
const flatEmbedding = await generateIntentEmbedding(sanitizedPayload);
|
|
483
498
|
const created = await this.database.createIntent({
|
|
484
499
|
userId: state.userId,
|
|
485
500
|
payload: sanitizedPayload,
|
|
@@ -532,19 +547,7 @@ export class IntentGraphFactory {
|
|
|
532
547
|
const matchedVerifiedIntent = verifiedIntentByPayload.get(updateAction.payload) ||
|
|
533
548
|
verifiedIntentByPayload.get(sanitizedPayload);
|
|
534
549
|
// Regenerate embedding for the updated payload
|
|
535
|
-
|
|
536
|
-
if (this.embedder) {
|
|
537
|
-
try {
|
|
538
|
-
const embedding = await this.embedder.generate(sanitizedPayload, undefined, getAbortSignalConfig());
|
|
539
|
-
flatEmbedding = Array.isArray(embedding?.[0])
|
|
540
|
-
? embedding[0]
|
|
541
|
-
: embedding;
|
|
542
|
-
logger.verbose("Generated embedding for updated intent", { intentId: updateAction.id, dimensions: flatEmbedding?.length });
|
|
543
|
-
}
|
|
544
|
-
catch (embErr) {
|
|
545
|
-
logger.error("Failed to generate embedding for intent update (continuing without)", { error: embErr });
|
|
546
|
-
}
|
|
547
|
-
}
|
|
550
|
+
const flatEmbedding = await generateIntentEmbedding(sanitizedPayload, updateAction.id);
|
|
548
551
|
const updated = await this.database.updateIntent(updateAction.id, {
|
|
549
552
|
payload: sanitizedPayload,
|
|
550
553
|
embedding: flatEmbedding,
|