feat: Enhance Mattermost integration by adding sync commands and context checks for recent messages
This commit is contained in:
@@ -12,6 +12,7 @@ Behavior rules:
|
|||||||
|
|
||||||
- Treat `README.md`, `ai/context/`, `ai/state/`, `knowledge/`, and `ai/logs/` as the persistent memory of the project.
|
- Treat `README.md`, `ai/context/`, `ai/state/`, `knowledge/`, and `ai/logs/` as the persistent memory of the project.
|
||||||
- Before answering a prompt that depends on current state, verify the latest relevant files instead of relying only on conversation history.
|
- Before answering a prompt that depends on current state, verify the latest relevant files instead of relying only on conversation history.
|
||||||
|
- If the prompt asks for the latest Mattermost message, the last message from Jeff/current manager, or what someone just said, force a Mattermost refresh before answering and do not rely on stale inbox context.
|
||||||
- For any meaningful prompt, decide whether the interaction adds, corrects, or sharpens project memory.
|
- For any meaningful prompt, decide whether the interaction adds, corrects, or sharpens project memory.
|
||||||
- When the user provides new durable information, update the right workspace files before or while answering.
|
- When the user provides new durable information, update the right workspace files before or while answering.
|
||||||
- If existing context is stale, correct it directly instead of leaving conflicting versions.
|
- If existing context is stale, correct it directly instead of leaving conflicting versions.
|
||||||
|
|||||||
40
.opencode/commands/latest-message.md
Normal file
40
.opencode/commands/latest-message.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
description: Force-sync Mattermost and answer from the latest matching message
|
||||||
|
---
|
||||||
|
|
||||||
|
Force-refresh Mattermost first, then answer the user's question from the refreshed inbox.
|
||||||
|
|
||||||
|
Use this when the user asks for:
|
||||||
|
|
||||||
|
- the latest or last message
|
||||||
|
- what Jeff or another person just said
|
||||||
|
- the latest Mattermost update
|
||||||
|
- the latest message in `fidelity-preguntas`
|
||||||
|
|
||||||
|
Run sync:
|
||||||
|
|
||||||
|
!`if [ -n "$FIDELITY_MATTERMOST_SYNC_CMD" ]; then bash -lc "$FIDELITY_MATTERMOST_SYNC_CMD"; elif [ -f scripts/mattermost/sync.sh ]; then bash scripts/mattermost/sync.sh; else echo "No Mattermost sync command is configured."; fi`
|
||||||
|
|
||||||
|
Read refreshed Mattermost context:
|
||||||
|
|
||||||
|
!`if [ -s ai/inbox/mattermost-latest.md ]; then cat ai/inbox/mattermost-latest.md; elif [ -s scripts/mattermost/generated/mattermost_context.jsonl ]; then cat scripts/mattermost/generated/mattermost_context.jsonl; else echo "No Mattermost context available after sync."; fi`
|
||||||
|
|
||||||
|
User request:
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
|
||||||
|
- Do not answer from old conversation memory.
|
||||||
|
- Use only the refreshed Mattermost output above.
|
||||||
|
- If the user asks for Jeff/current manager, filter messages by `jeff`, `jeff.dewitte`, or the current manager profile when visible.
|
||||||
|
- If multiple messages match, return the newest matching message first.
|
||||||
|
- Include timestamp, channel, sender, and concise summary.
|
||||||
|
- If the message changes project context, update the appropriate workspace memory after answering.
|
||||||
|
- If sync fails or no refreshed context is available, say that directly and do not infer from stale context.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
|
||||||
|
1. Latest matching message
|
||||||
|
2. Why it matters
|
||||||
|
3. Any memory update made
|
||||||
@@ -13,6 +13,8 @@ Run the command and use its output as fresh communication context:
|
|||||||
|
|
||||||
!`if [ -n "$FIDELITY_MATTERMOST_SYNC_CMD" ]; then bash -lc "$FIDELITY_MATTERMOST_SYNC_CMD"; elif [ -f scripts/mattermost/sync.sh ]; then bash scripts/mattermost/sync.sh; else echo "No Mattermost sync command is configured."; fi`
|
!`if [ -n "$FIDELITY_MATTERMOST_SYNC_CMD" ]; then bash -lc "$FIDELITY_MATTERMOST_SYNC_CMD"; elif [ -f scripts/mattermost/sync.sh ]; then bash scripts/mattermost/sync.sh; else echo "No Mattermost sync command is configured."; fi`
|
||||||
|
|
||||||
|
Use this command implicitly when the user asks for the latest or last Mattermost message, especially messages from Jeff or the current manager.
|
||||||
|
|
||||||
Then:
|
Then:
|
||||||
|
|
||||||
- if the command fails, stop there and do not edit any workspace files
|
- if the command fails, stop there and do not edit any workspace files
|
||||||
|
|||||||
@@ -35,6 +35,78 @@ async function resolveSyncContent(directory, stdoutText) {
|
|||||||
return (generated || "").trim()
|
return (generated || "").trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractPromptText(event) {
|
||||||
|
const candidates = [
|
||||||
|
event?.properties?.text,
|
||||||
|
event?.properties?.message,
|
||||||
|
event?.properties?.prompt,
|
||||||
|
event?.text,
|
||||||
|
event?.message,
|
||||||
|
event?.prompt,
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (typeof candidate === "string" && candidate.trim()) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(event)
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiresFreshMattermost(promptText) {
|
||||||
|
const text = promptText
|
||||||
|
.normalize("NFD")
|
||||||
|
.replace(/[\u0300-\u036f]/g, "")
|
||||||
|
.toLowerCase()
|
||||||
|
|
||||||
|
const freshnessTerms = [
|
||||||
|
"ultimo",
|
||||||
|
"ultimos",
|
||||||
|
"ultima",
|
||||||
|
"ultimas",
|
||||||
|
"latest",
|
||||||
|
"last",
|
||||||
|
"reciente",
|
||||||
|
"recent",
|
||||||
|
"nuevo",
|
||||||
|
"new",
|
||||||
|
"actualiza",
|
||||||
|
"sync",
|
||||||
|
"sincroniza",
|
||||||
|
"revisa",
|
||||||
|
"dijo",
|
||||||
|
"menciono",
|
||||||
|
"respondio",
|
||||||
|
"contesto",
|
||||||
|
"check",
|
||||||
|
"look at",
|
||||||
|
"said",
|
||||||
|
"mentioned",
|
||||||
|
"replied",
|
||||||
|
]
|
||||||
|
|
||||||
|
const mattermostTerms = [
|
||||||
|
"mattermost",
|
||||||
|
"mensaje",
|
||||||
|
"mensajes",
|
||||||
|
"message",
|
||||||
|
"messages",
|
||||||
|
"jeff",
|
||||||
|
"manager",
|
||||||
|
"fidelity-preguntas",
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
freshnessTerms.some((term) => text.includes(term)) &&
|
||||||
|
mattermostTerms.some((term) => text.includes(term))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const MattermostInbox = async ({ $, directory, client }) => {
|
export const MattermostInbox = async ({ $, directory, client }) => {
|
||||||
let lastSyncAt = 0
|
let lastSyncAt = 0
|
||||||
|
|
||||||
@@ -43,7 +115,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
|
|||||||
await writeFile(statusPath, `${JSON.stringify(data, null, 2)}\n`, "utf8")
|
await writeFile(statusPath, `${JSON.stringify(data, null, 2)}\n`, "utf8")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sync(reason) {
|
async function sync(reason, options = {}) {
|
||||||
const command = await resolveSyncCommand(directory)
|
const command = await resolveSyncCommand(directory)
|
||||||
if (!command) return
|
if (!command) return
|
||||||
|
|
||||||
@@ -54,7 +126,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
|
|||||||
const minIntervalMs = Math.max(1, Number.isNaN(intervalMinutes) ? 15 : intervalMinutes) * 60 * 1000
|
const minIntervalMs = Math.max(1, Number.isNaN(intervalMinutes) ? 15 : intervalMinutes) * 60 * 1000
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
if (now - lastSyncAt < minIntervalMs) return
|
if (!options.force && now - lastSyncAt < minIntervalMs) return
|
||||||
lastSyncAt = now
|
lastSyncAt = now
|
||||||
|
|
||||||
const inboxDir = path.join(directory, "ai/inbox")
|
const inboxDir = path.join(directory, "ai/inbox")
|
||||||
@@ -78,6 +150,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
|
|||||||
await writeStatus(statusPath, {
|
await writeStatus(statusPath, {
|
||||||
syncedAt: nowIso(),
|
syncedAt: nowIso(),
|
||||||
reason,
|
reason,
|
||||||
|
forced: Boolean(options.force),
|
||||||
changed: previous !== `${output}\n` && previous !== output,
|
changed: previous !== `${output}\n` && previous !== output,
|
||||||
commandConfigured: true,
|
commandConfigured: true,
|
||||||
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
|
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
|
||||||
@@ -86,6 +159,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
|
|||||||
await writeStatus(statusPath, {
|
await writeStatus(statusPath, {
|
||||||
syncedAt: nowIso(),
|
syncedAt: nowIso(),
|
||||||
reason,
|
reason,
|
||||||
|
forced: Boolean(options.force),
|
||||||
changed: false,
|
changed: false,
|
||||||
commandConfigured: true,
|
commandConfigured: true,
|
||||||
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
|
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
|
||||||
@@ -96,6 +170,7 @@ export const MattermostInbox = async ({ $, directory, client }) => {
|
|||||||
await writeStatus(statusPath, {
|
await writeStatus(statusPath, {
|
||||||
syncedAt: nowIso(),
|
syncedAt: nowIso(),
|
||||||
reason,
|
reason,
|
||||||
|
forced: Boolean(options.force),
|
||||||
changed: false,
|
changed: false,
|
||||||
commandConfigured: true,
|
commandConfigured: true,
|
||||||
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
|
commandSource: process.env.FIDELITY_MATTERMOST_SYNC_CMD?.trim() ? "env" : "workspace-default",
|
||||||
@@ -123,7 +198,14 @@ export const MattermostInbox = async ({ $, directory, client }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === "tui.prompt.append") {
|
if (event.type === "tui.prompt.append") {
|
||||||
await sync("tui.prompt.append")
|
const promptText = extractPromptText(event)
|
||||||
|
const forceFreshMattermost = requiresFreshMattermost(promptText)
|
||||||
|
await sync(
|
||||||
|
forceFreshMattermost
|
||||||
|
? "tui.prompt.append:fresh-mattermost-request"
|
||||||
|
: "tui.prompt.append",
|
||||||
|
{ force: forceFreshMattermost },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ These are also loaded through `opencode.json`.
|
|||||||
- Assume the workspace may contain stale context until checked.
|
- Assume the workspace may contain stale context until checked.
|
||||||
- Before answering questions that depend on current work state, inspect `ai/state/current.md` and the latest relevant log under `ai/logs/`.
|
- Before answering questions that depend on current work state, inspect `ai/state/current.md` and the latest relevant log under `ai/logs/`.
|
||||||
- If `ai/inbox/mattermost-latest.md` exists, inspect it for fresher communication context before answering standup, status, or manager-message prompts.
|
- If `ai/inbox/mattermost-latest.md` exists, inspect it for fresher communication context before answering standup, status, or manager-message prompts.
|
||||||
|
- If the user asks for the latest/last/recent Mattermost message, the latest message from Jeff/current manager, or what someone just said, synchronize Mattermost first instead of relying on existing inbox context.
|
||||||
|
- If automatic refresh is uncertain, use the explicit latest-message flow: run the Mattermost sync command, then answer from the refreshed inbox only.
|
||||||
- For any meaningful prompt, decide whether the interaction introduces or corrects project memory.
|
- For any meaningful prompt, decide whether the interaction introduces or corrects project memory.
|
||||||
- If a sync command, extraction script, or inbox refresh fails, do not update logs, state, or context files from that failed attempt.
|
- If a sync command, extraction script, or inbox refresh fails, do not update logs, state, or context files from that failed attempt.
|
||||||
- Treat sync failures as operational errors, not project context.
|
- Treat sync failures as operational errors, not project context.
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ Project commands live under `.opencode/commands/` and are intended to:
|
|||||||
- draft standups
|
- draft standups
|
||||||
- draft manager updates
|
- draft manager updates
|
||||||
- draft Copilot prompts for coding work on the Fidelity machine
|
- draft Copilot prompts for coding work on the Fidelity machine
|
||||||
|
- force-refresh and inspect latest Mattermost messages
|
||||||
- convert rough notes into daily log updates
|
- convert rough notes into daily log updates
|
||||||
|
|
||||||
This keeps AI output tied to the latest workspace state instead of relying on chat memory alone.
|
This keeps AI output tied to the latest workspace state instead of relying on chat memory alone.
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ When drafting messages for a manager or stakeholder:
|
|||||||
- Before answering Swift, SwiftUI, iOS architecture, testing, or debugging questions, check `ai/context/ios/` and use the project-local iOS skills when available
|
- Before answering Swift, SwiftUI, iOS architecture, testing, or debugging questions, check `ai/context/ios/` and use the project-local iOS skills when available
|
||||||
- Before generating a prompt for another AI or GitHub Copilot, check `ai/context/process/ai-to-ai-prompting.md` and make the prompt self-contained
|
- Before generating a prompt for another AI or GitHub Copilot, check `ai/context/process/ai-to-ai-prompting.md` and make the prompt self-contained
|
||||||
- If `ai/inbox/mattermost-latest.md` exists, check it before answering prompts about current status, standups, or supervisor communication
|
- If `ai/inbox/mattermost-latest.md` exists, check it before answering prompts about current status, standups, or supervisor communication
|
||||||
|
- If the user asks for the latest/last/recent Mattermost message, the latest message from Jeff/current manager, or what someone just said, synchronize Mattermost first and then answer from the refreshed inbox
|
||||||
|
- If automatic refresh is uncertain, use the explicit latest-message flow: run the Mattermost sync command, then answer from refreshed output only
|
||||||
- Treat all meaningful user prompts as potential memory updates, not only explicit sync commands
|
- Treat all meaningful user prompts as potential memory updates, not only explicit sync commands
|
||||||
- If a Mattermost sync or other context-ingestion step fails, do not update `ai/logs/`, `ai/state/`, or stable context files based on that failure
|
- If a Mattermost sync or other context-ingestion step fails, do not update `ai/logs/`, `ai/state/`, or stable context files based on that failure
|
||||||
- Do not record missing dependencies, failed sync attempts, or missing inbox files as project facts unless the user explicitly asks to track the operational issue
|
- Do not record missing dependencies, failed sync attempts, or missing inbox files as project facts unless the user explicitly asks to track the operational issue
|
||||||
|
|||||||
Reference in New Issue
Block a user