1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
-- | Tool for Ava to read her own audit logs and subagent traces.
--
-- Enables self-diagnosis: "Let me check my logs for that subagent run..."
--
-- : out omni-agent-tools-avalogs
-- : dep aeson
-- : dep time
module Omni.Agent.Tools.AvaLogs
( readAvaLogsTool,
main,
)
where
import Alpha
import Data.Aeson ((.=))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Text as Text
import qualified Data.Time as Time
import qualified Omni.Agent.AuditLog as AuditLog
import qualified Omni.Agent.Engine as Engine
main :: IO ()
main = putText "Omni.Agent.Tools.AvaLogs - no standalone execution"
readAvaLogsTool :: Engine.Tool
readAvaLogsTool =
Engine.Tool
{ Engine.toolName = "read_ava_logs",
Engine.toolDescription =
"Read Ava's audit logs or subagent traces for self-diagnosis. "
<> "Use to review past conversations, inspect subagent runs, or debug issues. "
<> "Pass subagent_id to view a specific subagent's trace, or last_n for recent Ava logs.",
Engine.toolJsonSchema =
Aeson.object
[ "type" .= ("object" :: Text),
"properties"
.= Aeson.object
[ "subagent_id"
.= Aeson.object
[ "type" .= ("string" :: Text),
"description" .= ("Subagent ID to view (e.g. 'abc123')" :: Text)
],
"last_n"
.= Aeson.object
[ "type" .= ("integer" :: Text),
"description" .= ("Number of recent log entries to return (default: 20)" :: Text)
]
]
],
Engine.toolExecute = executeReadLogs
}
executeReadLogs :: Aeson.Value -> IO Aeson.Value
executeReadLogs v = do
let maybeSubagentId = case v of
Aeson.Object obj -> case KeyMap.lookup "subagent_id" obj of
Just (Aeson.String sid) -> Just sid
_ -> Nothing
_ -> Nothing
let lastN = case v of
Aeson.Object obj -> case KeyMap.lookup "last_n" obj of
Just (Aeson.Number n) -> round n
_ -> 20
_ -> 20
case maybeSubagentId of
Just sid -> do
let subagentId = AuditLog.SubagentId sid
entries <- AuditLog.readSubagentLogs subagentId
pure
<| Aeson.object
[ "subagent_id" .= sid,
"entry_count" .= length entries,
"entries" .= map formatEntry entries
]
Nothing -> do
entries <- AuditLog.getRecentAvaLogs lastN
today <- Time.utctDay </ Time.getCurrentTime
pure
<| Aeson.object
[ "date" .= Time.formatTime Time.defaultTimeLocale "%Y-%m-%d" today,
"entry_count" .= length entries,
"entries" .= map formatEntry entries
]
formatEntry :: AuditLog.AuditLogEntry -> Aeson.Value
formatEntry entry =
Aeson.object
[ "timestamp" .= Time.formatTime Time.defaultTimeLocale "%H:%M:%S" (AuditLog.logTimestamp entry),
"event_type" .= tshow (AuditLog.logEventType entry),
"agent_id" .= AuditLog.unAgentId (AuditLog.logAgentId entry),
"content" .= summarizeContent (AuditLog.logContent entry)
]
summarizeContent :: Aeson.Value -> Text
summarizeContent (Aeson.String t) = Text.take 200 t
summarizeContent (Aeson.Object obj) =
let keys = KeyMap.keys obj
in "object with keys: " <> Text.intercalate ", " (map Key.toText keys)
summarizeContent (Aeson.Array arr) = "array with " <> tshow (length arr) <> " items"
summarizeContent Aeson.Null = "null"
summarizeContent (Aeson.Bool b) = if b then "true" else "false"
summarizeContent (Aeson.Number n) = tshow n
|