ofiere-openclaw-plugin 4.31.0 → 4.33.0

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.
Files changed (4) hide show
  1. package/index.ts +14 -3
  2. package/package.json +1 -1
  3. package/src/prompt.ts +145 -297
  4. package/src/tools.ts +106 -29
package/index.ts CHANGED
@@ -61,10 +61,21 @@ const ofierePlugin = {
61
61
  ready: false,
62
62
  };
63
63
 
64
- // ── Hook: inject Ofiere context into every agent prompt ────────────────
65
- // Using api.on()same pattern as Composio
64
+ // ── System prompt is built ONCE (it's static after init) ──────────────
65
+ // No hook needed here the brain context hook in tools.ts uses
66
+ // prependSystemContext via getSystemPromptCached() to combine both
67
+ // in a single hook invocation, eliminating one serial hook call.
68
+ let cachedSystemPrompt = "";
69
+ const getSystemPromptCached = () => {
70
+ if (!cachedSystemPrompt) {
71
+ cachedSystemPrompt = getSystemPrompt(promptState);
72
+ }
73
+ return cachedSystemPrompt;
74
+ };
75
+
76
+ // Hook: inject BOTH system prompt + brain context in one call
66
77
  api.on("before_prompt_build", () => ({
67
- prependSystemContext: getSystemPrompt(promptState),
78
+ prependSystemContext: getSystemPromptCached(),
68
79
  }));
69
80
 
70
81
  // ── Connect to Supabase and register tools ────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.31.0",
3
+ "version": "4.33.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM - 14 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, SOP management, and agent brain (memory + self-improvement)",
6
6
  "keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
package/src/prompt.ts CHANGED
@@ -1,172 +1,118 @@
1
1
  // src/prompt.ts — Dynamic system prompt for Ofiere PM plugin
2
2
  //
3
- // The prompt is built dynamically based on plugin state.
4
- // Tool documentation is structured so adding a new meta-tool
5
- // only requires adding a new entry to TOOL_DOCS below.
6
-
7
- // ─── Tool Documentation Registry ────────────────────────────────────────────
8
- // Add new meta-tool docs here when expanding. Each entry maps to one
9
- // registered meta-tool and will be included in the system prompt.
10
-
11
- const TOOL_DOCS: Record<string, string> = {
12
- OFIERE_TASK_OPS: `- **OFIERE_TASK_OPS** Manage tasks and approvals (action: "list", "create", "update", "delete", "add_approval", "list_approvals", "resolve_approval")
13
- - list: Filter by status, agent_id, space_id, folder_id, limit. Returns execution_plan, goals, constraints if present
14
- - create: Requires title. IMPORTANT: Always pass agent_id with your own name to self-assign (e.g. agent_id: "celia")
15
- - Optional: description, instructions, execution_plan, goals, constraints, system_prompt, priority, tags, dates
16
- - For COMPLEX tasks: include execution_plan (step-by-step), goals, constraints, and system_prompt
17
- - For SIMPLE tasks: just title and optionally description
18
- - update: Requires task_id. All create fields + progress
19
- - delete: Requires task_id. Removes task and subtasks
20
- - add_approval: Request sign-off on a task. Requires: task_id, approver_name. Optional: approver_type (human|agent, auto-detected), due_date, comment
21
- - list_approvals: List approvals. Optional: task_id to filter, approval_status (pending|approved|rejected)
22
- - resolve_approval: Approve or reject. Requires: approval_id, approval_status (approved|rejected). Optional: comment`,
23
-
24
- OFIERE_AGENT_OPS: `- **OFIERE_AGENT_OPS** — Query agents (action: "list")
25
- - list: See all agents with IDs, names, roles for task assignment`,
26
-
27
- OFIERE_PROJECT_OPS: `- **OFIERE_PROJECT_OPS** — Manage PM hierarchy (action: "list_spaces", "create_space", "update_space", "delete_space", "list_folders", "create_folder", "update_folder", "delete_folder", "list_dependencies", "add_dependency", "remove_dependency")
28
- - Spaces: Top-level containers. CRUD with name, icon, icon_color
29
- - Folders: Live inside spaces. Can nest via parent_folder_id. Types: folder, project
30
- - Dependencies: Link tasks with predecessor/successor relationships
31
- - Types: finish_to_start (default), start_to_start, finish_to_finish, start_to_finish
32
- - lag_days: optional delay between linked tasks`,
33
-
34
- OFIERE_SCHEDULE_OPS: `- **OFIERE_SCHEDULE_OPS** — Calendar events (action: "list", "create", "update", "delete")
35
- - list: Filter by start_date, end_date, agent_id
36
- - create: Requires title + scheduled_date (YYYY-MM-DD). Optional: scheduled_time, duration_minutes, task_id, agent_id, recurrence_type, color
37
- - Recurrence: none, hourly, daily, weekly, monthly with interval`,
38
-
39
- OFIERE_KNOWLEDGE_OPS: `- **OFIERE_KNOWLEDGE_OPS** Ofiere Knowledge Library (action: "search", "list", "create", "update", "delete")
40
- - ALWAYS use this tool when the user mentions "knowledge base", "knowledge library", "knowledge entries", or asks to recall/retrieve stored knowledge
41
- - search: Keyword search across all stored documents. Requires: query
42
- - list: Paginated listing of knowledge entries with full content. Optional: search filter
43
- - create: Add knowledge to the library. Requires: file_name. Optional: content, source, author, credibility_tier
44
- - update/delete: By document ID`,
45
-
46
- OFIERE_WORKFLOW_OPS: `- **OFIERE_WORKFLOW_OPS** Full workflow automation control (13 actions)
47
- - list: All workflows, filter by status (draft, active, paused, archived)
48
- - get: Full workflow details with all node IDs, types, data, edges — ALWAYS call this before surgical edits
49
- - create: New workflow with name + nodes[] and edges[]. A manual_trigger is auto-prepended if missing
50
- - update: Replace entire workflow graph (name, description, status, nodes, edges)
51
- - delete: Remove workflow and all run history
52
- - list_runs: Recent execution history for a workflow
53
- - trigger: Start a workflow run
54
- - **add_nodes**: Add new nodes to an existing workflow. Required: workflow_id, nodes[]. Optional: edges[] to connect them
55
- - **update_node**: Edit a specific node's data fields (e.g. change task instructions, agentId, template). Required: workflow_id, node_id, data. Only specified fields are changed — others are preserved
56
- - **delete_nodes**: Remove specific nodes and all their connected edges. Required: workflow_id, node_ids[]
57
- - **add_edges**: Add new connections between existing nodes. Required: workflow_id, edges[]. Each edge: { source, target, sourceHandle?, targetHandle? }
58
- - **delete_edges**: Remove specific edges by ID. Required: workflow_id, edge_ids[]
59
- - **insert_node_between**: Insert a new node between two connected nodes (auto-rewires edges). Required: workflow_id, source_node_id, target_node_id, node. Use this to add steps in the middle of a flow
60
- - Node types: manual_trigger, webhook_trigger, agent_step, formatter_step, http_request, task_call, variable_set, condition, human_approval, delay, loop, convergence, output, checkpoint, note
61
- - Key fields: agent_step(agentId, task, responseMode, timeoutSec), formatter_step(template, formatMode), condition(expression, varCheck, operator, varMatch), human_approval(instructions), variable_set(variableName, variableValue, operation)
62
- - Edge handles: condition edges use sourceHandle "condition-true"/"condition-false". Loop edges use "loop_body"/"done"
63
- - Variables: Use {{prev.nodeId.outputText}} for prior outputs, {{variables.key}} for stored variables`,
64
-
65
- OFIERE_NOTIFY_OPS: `- **OFIERE_NOTIFY_OPS** Notifications & Channel Reports (action: "list", "mark_read", "mark_all_read", "delete", "send_report", "get_task_detail", "schedule_report", "list_schedules", "delete_schedule")
66
- - list: Recent notifications. unread_only=true for unread only
67
- - mark_read: Mark one notification read by ID
68
- - mark_all_read: Mark all as read
69
- - send_report: Send a numbered PM progress report to YOUR connected channels (Telegram, Discord, Slack, etc.)
70
- - Required: scope_type (space|folder|project|task|all), agent_id (YOUR name, e.g. "thalia")
71
- - Optional: scope_id, channel_types[] (filter to specific channels), include_completed
72
- - The report is automatically generated from current PM data and sent through YOUR channel bindings ONLY
73
- - Tasks are numbered globally (1, 2, 3...) across Active/Pending/Done sections. Recurring tasks that executed recently appear in BOTH Done (today's run) and Pending (next cycle)
74
- - get_task_detail: Get the FULL result/content of a task by its report number
75
- - Required: task_number (1-indexed number from the report)
76
- - Optional: scope_type (default all), scope_id
77
- - Returns: complete task description (where execution results live), custom_fields, status, tags, and all metadata
78
- - Use when user asks "details on number X", "what's task 2 result", "show me #3", etc.
79
- - schedule_report: Create a recurring/scheduled report
80
- - Required: scope_type, recurrence_type (hourly|daily|weekly|monthly), agent_id (YOUR name)
81
- - Optional: scope_id, scope_label, channel_types[], recurrence_time (HH:MM UTC, default 09:00), recurrence_interval, recurrence_days_of_week (e.g. "mon,wed,fri"), include_completed
82
- - list_schedules: View all your active report schedules
83
- - delete_schedule: Remove a scheduled report by schedule_id`,
84
-
85
- OFIERE_MEMORY_OPS: `- **OFIERE_MEMORY_OPS** — Conversation history & knowledge memory (action: "list_conversations", "get_messages", "search_messages", "add_knowledge", "search_knowledge")
86
- - list_conversations: Recent chats, filter by agent_id
87
- - get_messages: Full message history for a conversation
88
- - search_messages: Search across all messages by keyword
89
- - add_knowledge: Store a knowledge fragment (requires agent_id, content, source)
90
- - search_knowledge: Search stored knowledge for an agent`,
91
-
92
- OFIERE_PROMPT_OPS: `- **OFIERE_PROMPT_OPS** Manage prompt instruction chunks (action: "list", "get", "create", "update", "delete")
93
- - list: All prompt chunks ordered by display order
94
- - create: New chunk with name (max 30 chars) + content. Optional: color (hex), category
95
- - update: Change name, content, color, category, or order
96
- - delete: Remove a chunk by ID
97
- - All modifications are logged for audit`,
98
-
99
- OFIERE_CONSTELLATION_OPS: `- **OFIERE_CONSTELLATION_OPS** Create, edit, delete, and manage OpenClaw agent architectures from chat (action: "list_agents", "get_agent", "read_file", "write_file", "create_agent", "delete_agent", "delete_file", "read_blueprint", "list_agent_mesh")
100
- - CRITICAL: Before creating or editing any agent, ALWAYS call read_blueprint first
101
- - list_agents: See all agents with codename, role, and file list
102
- - get_agent: Full details for one agent including file sizes and identity
103
- - read_file / write_file: Read or overwrite any agent markdown file (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, etc.)
104
- - create_agent: Scaffold a new agent with structured params (name, codename, role, emoji, mission, personality, etc.). Auto-registers in OpenClaw
105
- - delete_agent: ⚠️ PERMANENTLY delete an entire agent workspace and unregister. Requires confirm: true. ALWAYS ask user for explicit confirmation first — this is IRREVERSIBLE
106
- - delete_file: Remove a specific file from an agent workspace
107
- - read_blueprint: Read the OpenClaw Agent Blueprint — the canonical structure reference
108
- - list_agent_mesh: See the sovereignty map (which agent owns what domain)
109
- - New agents get workspace-<name>/ with IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, <CODENAME>.md, and skills/
110
- - All changes sync to the Constellation dashboard automatically`,
111
-
112
- OFIERE_FILE_OPS: `- **OFIERE_FILE_OPS** — Manage Space Files (action: "list_files", "list_folders", "create_folder", "create_text_file", "upload_file", "read_text_file", "rename_file", "rename_folder", "move_file", "move_folder", "delete_file", "delete_folder", "share_file", "unshare_file")
113
- - list_files: Files in a space. Required: space_id. Optional: folder_id, shared_only
114
- - list_folders: Folders in a space. Required: space_id. Optional: parent_folder_id
115
- - create_folder: New folder. Required: space_id, name. Optional: parent_folder_id
116
- - create_text_file: Create text file (md, txt, json, csv, etc.). Required: space_id, file_name, content. Optional: folder_id
117
- - upload_file: Upload binary (base64). Required: space_id, file_name, content_base64. Optional: folder_id, file_type
118
- - read_text_file: Read file content. Required: file_id. Use when task instructions reference a file with @[name](file:ID)
119
- - rename_file / rename_folder: Rename. Required: file_id/folder_id + new_name
120
- - move_file / move_folder: Move to target folder (null=root). Required: file_id/folder_id
121
- - delete_file: Remove file + storage. Required: file_id
122
- - delete_folder: Remove folder + all nested files recursively. Required: folder_id
123
- - share_file / unshare_file: Toggle shared status. Required: file_id
124
- - Files created here appear in the PM Space Files tab immediately`,
125
-
126
- OFIERE_PLAN_OPS: `- **OFIERE_PLAN_OPS** — Visual execution plan builder (action: "list", "get", "create", "update", "delete", "add_nodes", "execute")
127
- - list: All saved plans. Optional: space_id filter
128
- - get: Full plan with complete node tree. Required: plan_id
129
- - create: New plan. Required: name. Optional: description, space_id, nodes[] (initial tree)
130
- - update: Modify plan. Required: plan_id. Optional: name, description, nodes[] (full tree replace)
131
- - delete: Remove plan. Required: plan_id
132
- - add_nodes: Add nodes to existing plan. Required: plan_id, nodes[]. Optional: parent_node_id (null = add as root)
133
- - execute: Deploy plan into real PM tasks/folders/dependencies. Required: plan_id. Optional: create_folder (default true), create_scheduler (default true), space_id, folder_id
134
- - Node types: task, gate, milestone
135
- - Node fields: type, title, description, agent_id, priority (0-3), status (PENDING/IN_PROGRESS/DONE/FAILED), start_date, due_date, tags[], execution_steps[{text}], goals[{label, type?}], constraints[{label, type?}], system_prompt, children[], parallel (boolean)
136
- - Plans are visual DAG drafts — they don't become real tasks until you call "execute"
137
- - Execution maps ALL node fields into real tasks: execution_steps → custom_fields.execution_plan, goals → custom_fields.goals, constraints → custom_fields.constraints, system_prompt → custom_fields.system_prompt
138
- - The user can see and edit your plans in the Planning Tab of the dashboard in real-time`,
139
-
140
- OFIERE_SOP_OPS: `- **OFIERE_SOP_OPS** — Standard Operating Procedures for department chiefs (action: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template")
141
- - list_templates: See available SOP templates (built-in + user-created)
142
- - create: Create a new SOP. Required: agent_id, title, sop_data. Optional: department, status
143
- - list: List SOPs. Optional: agent_id to filter by department chief. RUNTIME READ PATH: use this to discover your active SOPs when complexity is 🔴 COMPLEX
144
- - get: Full SOP details with structured content. Required: sop_id. RUNTIME READ PATH: use this after "list" to load full SOP content for execution guidance
145
- - update: Modify SOP content/status. Required: sop_id. Optional: title, sop_data, status, department
146
- - delete: Remove an SOP. Required: sop_id
147
- - list_subagents: View staff under a chief. Required: chief_agent_id
148
- - apply_template: Create SOP from a saved template. Required: agent_id, template_id
149
- - sop_data is a JSON object: { title, objective, scope, prerequisites[{text,checked}], steps[{name,action,owner,output}], deliverables[], escalationRules[{trigger,escalateTo,priority}], successCriteria[{text,checked}], notes }
150
- - Status values: draft, active, archived
151
- - SOPs appear in the SOP Manager page immediately via real-time sync
152
- - ADAPTIVE PROTOCOL: Do NOT always load SOPs. See the SOP PROTOCOL section in Rules for when to load vs skip`,
153
-
154
- OFIERE_BRAIN_OPS: `- **OFIERE_BRAIN_OPS** — Agent memory, knowledge graph, and self-improvement (TMT/MAGMA architecture)
155
- - Memory Tiers: L1_focus (24h), L2_episode (days-weeks), L3_pattern (weeks-months), L4_rule (permanent guardrails), L5_persona (permanent identity)
156
- - save_memory: Store a memory. Required: content, tier. Optional: agent_id, source (auto|manual|reflection|tool), context_key, importance (1-10)
157
- - recall: Full-text search memories. Required: query. Optional: agent_id, tier, limit
158
- - delete_memory: Remove a memory. Required: memory_id
159
- - promote_memory: Move memory up a tier. Required: memory_id, new_tier
160
- - log_learning: Record as L4_rule guardrail. Required: title, category. Optional: agent_id, detail, severity
161
- - list_learnings: View active L4 guardrails. Optional: agent_id, category, limit
162
- - resolve_learning: Supersede a guardrail. Required: memory_id. Optional: resolution
163
- - save_entity: Add to Knowledge Graph. Required: label, node_type (entity|concept|event|action). Optional: agent_id, properties
164
- - link_entities: Connect graph nodes. Required: source_id, target_id, relation_type. Optional: graph_type, weight, evidence
165
- - query_graph: Search Knowledge Graph. Required: query. Optional: agent_id, node_type, limit
166
- - start_trajectory: Begin recording execution trace. Optional: agent_id, conversation_id
167
- - end_trajectory: Close trajectory. Required: trajectory_id, outcome (success|failure|partial). Optional: satisfaction_signal
168
- - get_brain_status: Full brain dashboard — memory tiers, graph stats, trajectory stats. Optional: agent_id
169
- - This is your SUBCONSCIOUS — use it instinctively, not deliberately`,
3
+ // v4.31.0: Tiered prompt loading strategy.
4
+ // Tier A (always loaded): Core rules + 1-line tool summaries (~2K tokens)
5
+ // Tier B (embedded docs): Full tool docs injected on-demand via meta-tool descriptions
6
+ //
7
+ // This cuts system prompt from ~12K → ~3K tokens, reducing TTFT by 40-60%.
8
+
9
+ // ─── Tier A: One-Line Tool Summaries ────────────────────────────────────────
10
+ // Each tool gets a concise one-liner. Full docs live in the tool's own
11
+ // description field (registered in tools.ts) where the LLM reads them
12
+ // only when it decides to use that tool.
13
+
14
+ const TOOL_SUMMARIES: Record<string, string> = {
15
+ OFIERE_TASK_OPS: "Manage tasks: list, create, update, delete, approvals",
16
+ OFIERE_AGENT_OPS: "Query agents: list all with IDs, names, roles",
17
+ OFIERE_PROJECT_OPS: "PM hierarchy: spaces, folders, dependencies",
18
+ OFIERE_SCHEDULE_OPS: "Calendar: list, create, update, delete events",
19
+ OFIERE_KNOWLEDGE_OPS: "Knowledge library: search, list, create, update, delete",
20
+ OFIERE_WORKFLOW_OPS: "Workflow automation: 13 actions for DAG-based flows",
21
+ OFIERE_NOTIFY_OPS: "Notifications & channel reports (Telegram/Discord/Slack/WhatsApp)",
22
+ OFIERE_MEMORY_OPS: "Conversation history & knowledge memory search",
23
+ OFIERE_PROMPT_OPS: "Manage prompt instruction chunks (CRUD + ordering)",
24
+ OFIERE_CONSTELLATION_OPS: "Create/edit/delete OpenClaw agents from chat",
25
+ OFIERE_FILE_OPS: "Space files: upload, read, move, share, delete",
26
+ OFIERE_PLAN_OPS: "Visual execution plan builder (DAG drafts → real tasks)",
27
+ OFIERE_SOP_OPS: "Standard Operating Procedures for department chiefs",
28
+ OFIERE_BRAIN_OPS: "Agent memory, knowledge graph, self-improvement (TMT/MAGMA)",
29
+ };
30
+
31
+ // ─── Tier B: Full Tool Documentation ────────────────────────────────────────
32
+ // These are used as tool descriptions in registerTools(), NOT in the system prompt.
33
+ // The LLM sees them only when exploring available tools or preparing a tool call.
34
+
35
+ export const TOOL_DOCS: Record<string, string> = {
36
+ OFIERE_TASK_OPS: `Manage tasks and approvals.
37
+ Actions: "list", "create", "update", "delete", "add_approval", "list_approvals", "resolve_approval"
38
+ - list: Filter by status, agent_id, space_id, folder_id, limit. Returns execution_plan, goals, constraints if present
39
+ - create: Requires title. IMPORTANT: Always pass agent_id with your own name to self-assign (e.g. agent_id: "celia")
40
+ - Optional: description, instructions, execution_plan, goals, constraints, system_prompt, priority, tags, dates
41
+ - For COMPLEX tasks: include execution_plan (step-by-step), goals, constraints, and system_prompt
42
+ - For SIMPLE tasks: just title and optionally description
43
+ - update: Requires task_id. All create fields + progress
44
+ - delete: Requires task_id. Removes task and subtasks
45
+ - add_approval: Request sign-off. Requires: task_id, approver_name. Optional: approver_type (human|agent), due_date, comment
46
+ - list_approvals: List approvals. Optional: task_id, approval_status (pending|approved|rejected)
47
+ - resolve_approval: Approve/reject. Requires: approval_id, approval_status. Optional: comment`,
48
+
49
+ OFIERE_AGENT_OPS: `Query agents. Action: "list" see all agents with IDs, names, roles for task assignment.`,
50
+
51
+ OFIERE_PROJECT_OPS: `Manage PM hierarchy.
52
+ Actions: "list_spaces", "create_space", "update_space", "delete_space", "list_folders", "create_folder", "update_folder", "delete_folder", "list_dependencies", "add_dependency", "remove_dependency"
53
+ - Spaces: Top-level containers. CRUD with name, icon, icon_color
54
+ - Folders: Inside spaces. Nest via parent_folder_id. Types: folder, project
55
+ - Dependencies: Link tasks. Types: finish_to_start (default), start_to_start, finish_to_finish, start_to_finish. lag_days optional`,
56
+
57
+ OFIERE_SCHEDULE_OPS: `Calendar events.
58
+ Actions: "list", "create", "update", "delete"
59
+ - list: Filter by start_date, end_date, agent_id
60
+ - create: Requires title + scheduled_date (YYYY-MM-DD). Optional: scheduled_time, duration_minutes, task_id, agent_id, recurrence_type, color
61
+ - Recurrence: none, hourly, daily, weekly, monthly with interval`,
62
+
63
+ OFIERE_KNOWLEDGE_OPS: `Ofiere Knowledge Library.
64
+ Actions: "search", "list", "create", "update", "delete"
65
+ - ALWAYS use when user mentions "knowledge base", "knowledge library", or asks to recall stored knowledge
66
+ - search: Keyword search. Requires: query
67
+ - list: Paginated listing with full content. Optional: search filter
68
+ - create: Requires: file_name. Optional: content, source, author, credibility_tier
69
+ - update/delete: By document ID`,
70
+
71
+ OFIERE_WORKFLOW_OPS: `Full workflow automation (13 actions).
72
+ Actions: "list", "get", "create", "update", "delete", "list_runs", "trigger", "add_nodes", "update_node", "delete_nodes", "add_edges", "delete_edges", "insert_node_between"
73
+ - ALWAYS call "get" before modifying to see node IDs and graph structure
74
+ - Node types: manual_trigger, webhook_trigger, agent_step, formatter_step, http_request, task_call, variable_set, condition, human_approval, delay, loop, convergence, output, checkpoint, note
75
+ - Edge handles: condition edges use "condition-true"/"condition-false". Loop edges use "loop_body"/"done"
76
+ - Variables: {{prev.nodeId.outputText}}, {{variables.key}}`,
77
+
78
+ OFIERE_NOTIFY_OPS: `Notifications & Channel Reports.
79
+ Actions: "list", "mark_read", "mark_all_read", "delete", "send_report", "get_task_detail", "schedule_report", "list_schedules", "delete_schedule"
80
+ - send_report: Send PM progress report to YOUR channels. Required: scope_type, agent_id (YOUR name)
81
+ - get_task_detail: Get full task result by report number. Required: task_number
82
+ - schedule_report: Recurring reports. Required: scope_type, recurrence_type, agent_id`,
83
+
84
+ OFIERE_MEMORY_OPS: `Conversation history & knowledge memory.
85
+ Actions: "list_conversations", "get_messages", "search_messages", "add_knowledge", "search_knowledge"`,
86
+
87
+ OFIERE_PROMPT_OPS: `Manage prompt instruction chunks.
88
+ Actions: "list", "get", "create", "update", "delete"
89
+ - create: New chunk with name (max 30 chars) + content. Optional: color (hex), category`,
90
+
91
+ OFIERE_CONSTELLATION_OPS: `Create/edit/delete OpenClaw agents from chat.
92
+ Actions: "list_agents", "get_agent", "read_file", "write_file", "create_agent", "delete_agent", "delete_file", "read_blueprint", "list_agent_mesh"
93
+ - CRITICAL: ALWAYS call read_blueprint before creating/editing agents
94
+ - delete_agent: IRREVERSIBLE. Always ask user for explicit confirmation first`,
95
+
96
+ OFIERE_FILE_OPS: `Manage Space Files.
97
+ Actions: "list_files", "list_folders", "create_folder", "create_text_file", "upload_file", "read_text_file", "rename_file", "rename_folder", "move_file", "move_folder", "delete_file", "delete_folder", "share_file", "unshare_file"
98
+ - read_text_file: Use when task references @[name](file:ID)
99
+ - Files appear in PM Space Files tab immediately`,
100
+
101
+ OFIERE_PLAN_OPS: `Visual execution plan builder.
102
+ Actions: "list", "get", "create", "update", "delete", "add_nodes", "execute"
103
+ - Plans are visual DAG drafts not real tasks until "execute"
104
+ - Node types: task, gate, milestone
105
+ - Execution maps ALL fields: execution_steps, goals, constraints, system_prompt`,
106
+
107
+ OFIERE_SOP_OPS: `Standard Operating Procedures for department chiefs.
108
+ Actions: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template"
109
+ - sop_data: { title, objective, scope, prerequisites[], steps[], deliverables[], escalationRules[], successCriteria[], notes }
110
+ - See SOP PROTOCOL section for when to load vs skip`,
111
+
112
+ OFIERE_BRAIN_OPS: `Agent memory, knowledge graph, self-improvement (TMT/MAGMA).
113
+ Memory Tiers: L1_focus (24h), L2_episode (days), L3_pattern (weeks), L4_rule (permanent), L5_persona (permanent)
114
+ Actions: save_memory, recall, delete_memory, promote_memory, log_learning, list_learnings, resolve_learning, save_entity, link_entities, query_graph, start_trajectory, end_trajectory, get_brain_status
115
+ - This is your SUBCONSCIOUS — use instinctively, not deliberately`,
170
116
  };
171
117
 
172
118
  export function getSystemPrompt(state: {
@@ -184,8 +130,10 @@ export function getSystemPrompt(state: {
184
130
  ? `When you create a task without specifying agent_id, it is assigned to YOU (${state.agentId}).`
185
131
  : `When you create a task without specifying agent_id, it is assigned to YOU automatically.`;
186
132
 
187
- // Build tool docs from registry only include docs for tools that exist
188
- const toolDocs = Object.values(TOOL_DOCS).join("\n");
133
+ // Tier A: Compact tool index (~300 tokens vs ~3000 for full docs)
134
+ const toolIndex = Object.entries(TOOL_SUMMARIES)
135
+ .map(([name, desc]) => `- **${name}** — ${desc}`)
136
+ .join("\n");
189
137
 
190
138
  return `<ofiere-pm>
191
139
  You are connected to the Ofiere Project Management dashboard via the Ofiere PM plugin.
@@ -194,142 +142,42 @@ ${agentLine}
194
142
  ## Your Ofiere PM Tools (${state.toolCount} meta-tools)
195
143
 
196
144
  Each tool uses an "action" parameter to select the operation. Always include action.
145
+ Full parameter docs are in each tool's description — call the tool to see details.
197
146
 
198
- ${toolDocs}
147
+ ${toolIndex}
199
148
 
200
149
  ## Rules
201
- - ALWAYS pass agent_id with your own name when creating tasks (e.g. agent_id: "ivy"). Auto-detection is NOT reliable.
150
+ - ALWAYS pass agent_id with your own name when creating tasks (e.g. agent_id: "ivy").
202
151
  - ${assignRule}
203
152
  - To create an unassigned task, pass agent_id as "none" or "unassigned".
204
- - When the user says "create a task for [agent name]", use OFIERE_AGENT_OPS action:"list" to find the agent ID, then use OFIERE_TASK_OPS action:"create" with that agent_id.
205
- - Always confirm task creation/updates by reporting back what was done.
206
- - Task statuses: PENDING, IN_PROGRESS, DONE, FAILED.
207
- - Priority levels: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL.
208
- - Changes appear in the Ofiere dashboard immediately via real-time sync.
209
- - Do NOT fabricate task IDs — use OFIERE_TASK_OPS action:"list" to look up real IDs.
210
- - For complex tasks, ALWAYS include execution_plan, goals, and constraints. For simple tasks, just title is enough.
211
- - When creating dependencies, use OFIERE_PROJECT_OPS to link predecessor/successor tasks.
212
- - Prompt chunk modifications (OFIERE_PROMPT_OPS) are powerful use thoughtfully as they change agent behavior.
213
- - When the user asks about "knowledge base", "knowledge library", "knowledge entries", or wants to recall stored knowledge, ALWAYS use OFIERE_KNOWLEDGE_OPS do NOT rely on your own memory for this.
214
- - When creating or editing an agent's architecture, ALWAYS use OFIERE_CONSTELLATION_OPS action:"read_blueprint" first to understand the required structure.
215
- - When creating a new agent, use OFIERE_CONSTELLATION_OPS action:"create_agent" with all available structured params. The agent will be auto-registered in OpenClaw.
216
- - When deleting an agent, ALWAYS ask the user for explicit confirmation BEFORE calling OFIERE_CONSTELLATION_OPS action:"delete_agent" with confirm: true. Show them what will be deleted. This action is IRREVERSIBLE.
217
- - WORKFLOW MASTERY: When modifying existing workflows, ALWAYS call "get" first to see all node IDs and the current graph structure.
218
- - To add a step in the middle of a flow, use "insert_node_between" with the source and target node IDs. Do NOT rebuild the entire graph.
219
- - To change a node's configuration (e.g. update agent instructions, change template text), use "update_node" with just the fields that changed.
220
- - When creating workflows with condition or loop nodes, specify sourceHandle on edges: "condition-true"/"condition-false" for conditions, "loop_body"/"done" for loops.
221
- - Fill node data fields with actual content — include real task instructions, templates, and variable references. Do NOT leave fields empty unless intentionally blank.
222
- - Use add_approval when a task needs sign-off from a human or another agent before proceeding. Approvers can be agents (by name) or humans.
223
- - Task approvals (OFIERE_TASK_OPS) are SEPARATE from workflow gate approvals (human_approval nodes). Do not confuse them.
224
- - When an agent completes critical work, consider adding an approval request for human review before marking the task DONE.
225
- - When task instructions or system prompts contain file references like @[filename](file:FILE_ID), use OFIERE_FILE_OPS action:"read_text_file" file_id:"FILE_ID" to read the file content. Do NOT ask the user for the file — retrieve it yourself.
226
- - Use OFIERE_FILE_OPS to create output files (reports, data, configs) in the Space Files explorer. Prefer create_text_file for text-based outputs.
227
- - To save task output as a file, call OFIERE_FILE_OPS action:"create_text_file" with the space_id from the task context.
228
- - CHANNEL REPORTS: When the user asks you to "send a report", "send progress", "update me on Telegram/Discord/Slack/WhatsApp", use OFIERE_NOTIFY_OPS action:"send_report" with agent_id set to YOUR name. ALWAYS include agent_id — without it the report will fail. The report is generated from live PM data and sent through YOUR connected channels ONLY — not other agents' channels. Tasks are numbered (1, 2, 3...) for easy reference.
229
- - TASK DRILL-DOWN: When the user asks "details on number X", "what's task X result", "show me #X", or any variation referencing a numbered task from a report, use OFIERE_NOTIFY_OPS action:"get_task_detail" with task_number set to the number they mentioned. The description field contains the actual execution result (e.g. scan findings, analysis output). Present the description content as the primary response — that IS the task result.
230
- - To set up recurring reports (e.g. "send me a daily report at 9am"), use OFIERE_NOTIFY_OPS action:"schedule_report" with scope_type, recurrence_type, and agent_id set to YOUR name.
231
- - If a report is too long for the channel's message limit, save the full report as a markdown file using OFIERE_FILE_OPS action:"create_text_file" and send a summary to the channel instead.
232
- - PLANNING WORKFLOW: Use OFIERE_PLAN_OPS to build complex multi-step execution flows BEFORE creating individual tasks. Build the plan → let the user review in the Planning Tab → execute when approved.
233
- - When creating a plan with nodes, nest children inside each node's children[] array. Sequential children execute in order; set parallel: true on a parent node to fork its children into parallel branches.
234
- - Always call OFIERE_PLAN_OPS action:"get" before action:"add_nodes" or action:"update" to see the current tree structure and node IDs.
235
- - Execution ("execute") maps plan nodes 1:1 into real PM tasks with ALL enrichment fields preserved: execution_steps, goals, constraints, system_prompt. No data is lost in the handoff.
236
- - SOP CREATION: When asked to create an SOP, use OFIERE_SOP_OPS action:"create" with a COMPLETE sop_data object. Fill ALL fields with actionable, department-specific content — do NOT leave fields empty.
237
- - Each SOP step MUST have: a clear name, a specific action description, an assigned owner, and a concrete expected output.
238
- - Include escalation rules with appropriate priority levels (P1=critical blockers, P2=scope changes, P3=advisory).
239
- - When creating SOPs for department chiefs (Thalia=CMO, Ivy=COO, Daisy=CTO-Intel, Celia=CTO-Eng), tailor content to their domain expertise.
240
- - Prerequisites should be actionable checklist items. Success criteria should be measurable outcomes.
241
- - After creating an SOP, suggest the agent set it to "active" status when ready for execution.
242
- - PLANNING GATE: Before creating ANY task with 3+ execution steps, a due_date, or when the user describes a multi-phase project, ALWAYS ask: "Should I create a Plan first so you can review the structure before I create individual tasks?" If the user says yes, use OFIERE_PLAN_OPS. If they say no or it's a simple one-shot task, proceed directly with OFIERE_TASK_OPS.
243
- - TASK PLACEMENT — SOP-AWARE ROUTING: When creating a task, the system auto-assigns a PM space. But for FOLDER placement, follow this protocol:
244
- 1. If the user explicitly provides space_id or folder_id, use those directly.
245
- 2. If not, and you have active SOPs (loaded via 🔴 COMPLEX assessment), check if any SOP defines an operating structure or folder routing (e.g. "place marketing tasks in Marketing/Campaigns"). Follow the SOP's structure.
246
- 3. If no SOP guidance exists, check the Space Files tab for an operating map/structure document using OFIERE_FILE_OPS action:"list_files". If found, read it with "read_text_file" and follow its routing rules.
247
- 4. If no routing guidance exists at all, ask the user: "I can place this task in your default space root, or create a new folder/project for it. Which do you prefer?"
248
- 5. Do NOT blindly dump all SOPs to check routing — smart-select only SOPs whose title/department matches the task domain.
249
-
250
- ## SOP PROTOCOL — Adaptive Complexity Assessment
251
-
252
- Before executing any user request, classify its complexity to decide whether to load your SOPs:
253
-
254
- ### User Override (HIGHEST PRIORITY — always check first)
255
- - If user says "apply full SOP", "use SOP", "follow the SOP", "full protocol" → treat as 🔴 COMPLEX regardless of your classification
256
- - If user says "no SOP", "skip SOP", "just do it", "keep it simple", "quick" → treat as 🟢 SIMPLE regardless of your classification
257
- - User explicit intent ALWAYS overrides your classification
258
-
259
- ### 🟢 SIMPLE (Skip SOPs — zero overhead)
260
- Single tool call, direct data retrieval, simple CRUD, status checks, quick answers.
261
- Criteria: Can be completed with ≤2 tool calls, no cross-agent coordination, no multi-phase execution.
262
- Examples: "Check my email", "List tasks", "Create a quick task", "What's the project status?", "Tell me about X"
263
- Action: Execute directly. Do NOT call OFIERE_SOP_OPS. Do NOT mention SOPs.
264
-
265
- ### 🟡 MODERATE (Ask User)
266
- Multi-step work but clear execution path, no subagent delegation required, single-department scope.
267
- Criteria: 3-5 tool calls, single department, no escalation risk, no staff/subagent coordination needed.
268
- Examples: "Draft a marketing plan", "Set up a new workflow", "Analyze this report"
269
- Action: Ask the user briefly: "This task has moderate complexity. Do we need to apply full SOPs for this?"
270
- - If user says yes → escalate to 🔴 COMPLEX behavior
271
- - If user says no → proceed without SOPs
272
-
273
- ### 🔴 COMPLEX (Auto-Load SOPs)
274
- Multi-phase execution, cross-department coordination, subagent delegation, production impact, escalation risk.
275
- Criteria: 5+ tool calls, multiple departments involved, needs staff/subagent coordination, or has escalation risk.
276
- Examples: "Launch full marketing campaign", "Execute deployment pipeline", "Restructure operations", "Full audit"
277
- Action:
278
- 1. Call OFIERE_SOP_OPS action:"list" agent_id:"YOUR_NAME" to discover your active SOPs (returns titles + IDs)
279
- 2. Review the SOP titles — select ONLY the SOPs relevant to the current task. Skip unrelated SOPs entirely. Do NOT load all SOPs blindly.
280
- 3. For each RELEVANT SOP only: call OFIERE_SOP_OPS action:"get" sop_id:"..." to load full content
281
- 4. Follow loaded SOPs as your execution framework:
282
- - Prerequisites → validate ALL are met before starting (gate check)
283
- - Steps → use as your execution plan (follow in order, each step has name/action/owner/output)
284
- - Escalation Rules → apply when blockers arise or scope changes (P1=critical, P2=scope, P3=advisory)
285
- - Success Criteria → verify ALL are met before marking task DONE
286
- 5. If an SOP step assigns an "owner" that maps to one of your subagents, coordinate with that subagent
287
- 6. Announce: "Applying [SOP_TITLE] protocol for this execution." (only name the SOPs you actually loaded)
288
-
289
- ### Classification Transparency
290
- - When you load SOPs (🔴): state which SOP(s) you're following
291
- - When you ask about SOPs (🟡): keep the question brief and direct
292
- - When you skip SOPs (🟢): do NOT mention SOPs at all — just execute silently
153
+ - When user says "create a task for [agent]", use OFIERE_AGENT_OPS action:"list" first.
154
+ - Task statuses: PENDING, IN_PROGRESS, DONE, FAILED. Priority: 0=LOW, 1=MED, 2=HIGH, 3=CRITICAL.
155
+ - Changes appear in the dashboard immediately via real-time sync.
156
+ - Do NOT fabricate task IDs — always look up real IDs first.
157
+ - For complex tasks, include execution_plan, goals, constraints. For simple tasks, just title.
158
+ - When user asks about "knowledge base/library", ALWAYS use OFIERE_KNOWLEDGE_OPS.
159
+ - CONSTELLATION: ALWAYS read_blueprint before creating/editing agents. ALWAYS confirm before delete.
160
+ - WORKFLOWS: ALWAYS "get" before modifying. Use "insert_node_between" for mid-flow additions.
161
+ - CHANNEL REPORTS: ALWAYS include agent_id (YOUR name) in send_report. Use get_task_detail for drill-down.
162
+ - **PLANNING GATE (MANDATORY):** Before creating ANY task with 3+ execution steps, goals, constraints, OR a due_date, you MUST ask the user: "Would you like me to plan this first, or create the task directly?" If the user chooses to plan, use OFIERE_PLAN_OPS to create a visual plan first, then execute it to generate the real tasks. Do NOT skip this step.
163
+ - File refs @[name](file:ID): Use OFIERE_FILE_OPS read_text_file to retrieve don't ask user.
164
+ - Task approvals (OFIERE_TASK_OPS) workflow gate approvals (human_approval nodes).
165
+
166
+ ## SOP PROTOCOL Adaptive Complexity
167
+
168
+ Classify complexity before executing:
169
+ - **User Override**: "apply full SOP"/"use SOP" 🔴 COMPLEX. "no SOP"/"just do it" 🟢 SIMPLE.
170
+ - **🟢 SIMPLE** (≤2 tool calls, no coordination): Execute directly. Do NOT mention SOPs.
171
+ - **🟡 MODERATE** (3-5 calls, single dept): Ask briefly if SOPs needed.
172
+ - **🔴 COMPLEX** (5+ calls, multi-dept, escalation risk): Auto-load relevant SOPs via list→get.
293
173
 
294
174
  ## Agent Brain Protocol (TMT Subconscious)
295
175
 
296
- Your brain persists across conversations via the TMT (Temporal Memory Tree) hierarchy. At startup, your L5 persona, L4 guardrails, and L1 focus memories are injected automatically.
297
-
298
- ### Auto-Memory (OFIERE_BRAIN_OPS save_memory)
299
- After interactions where ANY of these occur, call save_memory:
300
- - User shares important context L2_episode, source: "auto"
301
- - You complete a task with noteworthy results → L2_episode, source: "tool"
302
- - You observe important system behavior → L2_episode, source: "auto"
303
- - Something needs immediate working context → L1_focus (auto-expires 24h)
304
- - A pattern emerges across multiple episodes → L3_pattern, source: "reflection"
305
-
306
- ### Auto-Learn (OFIERE_BRAIN_OPS log_learning)
307
- After interactions where ANY of these occur, call log_learning (stored as L4_rule):
308
- - User corrects you → category: "correction", severity: "medium"
309
- - A tool call fails or returns an error → category: "error"
310
- - User asks for something you can't do → category: "feature_request"
311
- - Your knowledge or assumption was wrong → category: "insight"
312
- - You discover a better approach → category: "best_practice"
313
-
314
- ### Knowledge Graph (OFIERE_BRAIN_OPS save_entity/link_entities)
315
- When you encounter key entities, concepts, or causal relationships:
316
- - Create entities for users, projects, tools, or recurring concepts
317
- - Link entities with typed edges (causes, depends_on, related_to)
318
- - Query the graph when reasoning about relationships or dependencies
319
-
320
- ### Trajectory Recording (OFIERE_BRAIN_OPS start_trajectory/end_trajectory)
321
- For complex multi-step tasks:
322
- - Call start_trajectory before executing a tool chain
323
- - Call end_trajectory with outcome when the chain completes
324
- - This feeds the ReasoningBank for autonomous self-improvement
325
-
326
- ### Rules
327
- - These calls happen AFTER your response to the user — never delay your reply to save a memory
328
- - Do NOT announce "saving memory" or "logging learning" to the user — this is subconscious
329
- - Do NOT save trivial interactions (greetings, confirmations, simple CRUD) — only signal worth remembering
330
- - Keep memory content concise: 1-3 sentences max. No system junk, keep it human
331
- - When you see your L4 guardrails at startup, actively avoid violating them
332
- - Use promote_memory when a pattern solidifies: L2→L3→L4→L5
176
+ Your brain persists across conversations. L5 persona, L4 guardrails, L1 focus are injected at startup.
177
+ - Save memories after significant interactions (not greetings/CRUD). Keep content to 1-3 sentences.
178
+ - Log learnings on corrections, errors, feature requests, insights, best practices.
179
+ - Do NOT announce memory operations this is subconscious.
180
+ - Never delay your reply to save a memory.
333
181
  </ofiere-pm>`;
334
182
  }
335
183
 
package/src/tools.ts CHANGED
@@ -198,7 +198,7 @@ function registerTaskOps(
198
198
  `Actions:\n` +
199
199
  `- "list": List/filter tasks. Optional: status, agent_id, space_id, folder_id, task_id, limit\n` +
200
200
  `- "get": Get a single task by ID. Required: task_id\n` +
201
- `- "create": Create a task. Required: title. Optional: agent_id, description, status, priority, space_id, folder_id, start_date, due_date, tags, instructions, execution_plan, goals, constraints, system_prompt, recurrence_type, recurrence_interval, scheduled_time\n` +
201
+ `- "create": Create a task. Required: title. Optional: agent_id, description, status, priority, space_id, folder_id, start_date, due_date, tags, instructions, execution_plan, goals, constraints, system_prompt, recurrence_type, recurrence_interval, scheduled_time. ⚠️ PLANNING GATE: If the task has 3+ execution steps, goals, or constraints, FIRST ask the user "Plan first or create directly?" If they choose to plan, use OFIERE_PLAN_OPS instead.\n` +
202
202
  `- "update": Update a task. Required: task_id. Optional: all create fields + progress\n` +
203
203
  `- "delete": Delete task + subtasks. Required: task_id\n` +
204
204
  `- "add_approval": Request approval on a task. Required: task_id, approver_name. Optional: approver_type (human|agent, auto-detected), due_date, comment\n` +
@@ -241,7 +241,7 @@ function registerTaskOps(
241
241
  folder_id: { type: "string", description: "PM Folder ID" },
242
242
  start_date: { type: "string", description: "Start date (ISO 8601). Required for scheduled/recurring tasks." },
243
243
  due_date: { type: "string", description: "Due date (ISO 8601)" },
244
- scheduled_time: { type: "string", description: "Time to execute in HH:MM format (UTC). If omitted, extracted from start_date or defaults to now+60s." },
244
+ scheduled_time: { type: "string", description: "Time to execute in HH:MM format (user's LOCAL time, e.g. 10:00 for 10 AM WIB). The system converts to UTC automatically." },
245
245
  recurrence_type: {
246
246
  type: "string",
247
247
  description: "How often the task recurs. 'none' for one-shot.",
@@ -542,8 +542,12 @@ async function handleCreateTask(
542
542
  // ── Auto-create scheduler event if task has a start_date ──────────────
543
543
  // This bridges the plugin → scheduler so the pg_cron task-dispatcher
544
544
  // Edge Function picks up the task at the right time.
545
+ //
546
+ // TIMEZONE: scheduled_time is treated as the user's LOCAL time (default WIB, UTC+7).
547
+ // We convert to UTC epoch for next_run_at so the edge function fires correctly.
545
548
  const startDate = params.start_date as string | undefined;
546
549
  const effectiveAgentId = (insertData.agent_id as string) || assignee;
550
+ const WIB_OFFSET_HOURS = 7; // Asia/Jakarta = UTC+7
547
551
  if (startDate && effectiveAgentId) {
548
552
  try {
549
553
  // Parse start_date robustly — it can be:
@@ -563,23 +567,41 @@ async function handleCreateTask(
563
567
  const hasTimeInfo = /[T ]\d{2}:\d{2}/.test(startDate);
564
568
 
565
569
  if (explicitScheduledTime) {
566
- // Agent explicitly passed a scheduled_time — use date from start_date + explicit time
570
+ // Agent explicitly passed a scheduled_time — treat as WIB local time
567
571
  const dateStr = parsedDate.toISOString().split("T")[0]; // YYYY-MM-DD
568
- const dt = new Date(`${dateStr}T${explicitScheduledTime}:00Z`);
572
+ const [localH, localM] = explicitScheduledTime.split(":").map(Number);
573
+ const utcH = localH - WIB_OFFSET_HOURS;
574
+ const dt = new Date(`${dateStr}T00:00:00Z`);
575
+ dt.setUTCHours(utcH, localM, 0, 0);
569
576
  nextRunAtEpoch = Math.floor(dt.getTime() / 1000);
570
- scheduledTimeFinal = explicitScheduledTime;
577
+ scheduledTimeFinal = explicitScheduledTime; // Store as user's local time
571
578
  scheduledDateFinal = dateStr;
572
579
  } else if (hasTimeInfo) {
573
- // start_date already contains time — use it directly
574
- nextRunAtEpoch = Math.floor(parsedDate.getTime() / 1000);
575
- scheduledTimeFinal = `${String(parsedDate.getUTCHours()).padStart(2, "0")}:${String(parsedDate.getUTCMinutes()).padStart(2, "0")}`;
576
- scheduledDateFinal = parsedDate.toISOString().split("T")[0];
580
+ // start_date already contains time — assume it's in the user's local timezone (WIB)
581
+ // Extract the local hour:minute from the string, NOT from UTC parsing
582
+ const timeMatch = startDate.match(/(\d{2}):(\d{2})/);
583
+ if (timeMatch) {
584
+ const localH = parseInt(timeMatch[1], 10);
585
+ const localM = parseInt(timeMatch[2], 10);
586
+ const dateStr = parsedDate.toISOString().split("T")[0];
587
+ const dt = new Date(`${dateStr}T00:00:00Z`);
588
+ dt.setUTCHours(localH - WIB_OFFSET_HOURS, localM, 0, 0);
589
+ nextRunAtEpoch = Math.floor(dt.getTime() / 1000);
590
+ scheduledTimeFinal = `${String(localH).padStart(2, "0")}:${String(localM).padStart(2, "0")}`;
591
+ scheduledDateFinal = dateStr;
592
+ } else {
593
+ // Can't extract time — fall back to UTC parsing
594
+ nextRunAtEpoch = Math.floor(parsedDate.getTime() / 1000);
595
+ scheduledTimeFinal = `${String(parsedDate.getUTCHours()).padStart(2, "0")}:${String(parsedDate.getUTCMinutes()).padStart(2, "0")}`;
596
+ scheduledDateFinal = parsedDate.toISOString().split("T")[0];
597
+ }
577
598
  } else {
578
- // Date only, no time — default to 09:00 UTC
599
+ // Date only, no time — default to 09:00 WIB (= 02:00 UTC)
579
600
  const dateStr = parsedDate.toISOString().split("T")[0];
580
- const dt = new Date(`${dateStr}T09:00:00Z`);
601
+ const dt = new Date(`${dateStr}T00:00:00Z`);
602
+ dt.setUTCHours(9 - WIB_OFFSET_HOURS, 0, 0, 0); // 09:00 WIB = 02:00 UTC
581
603
  nextRunAtEpoch = Math.floor(dt.getTime() / 1000);
582
- scheduledTimeFinal = "09:00";
604
+ scheduledTimeFinal = "09:00"; // Stored as WIB local time
583
605
  scheduledDateFinal = dateStr;
584
606
  }
585
607
  } else {
@@ -611,6 +633,7 @@ async function handleCreateTask(
611
633
  next_run_at: nextRunAtEpoch,
612
634
  run_count: 0,
613
635
  priority: params.priority !== undefined ? params.priority : 1,
636
+ timezone: "Asia/Jakarta",
614
637
  });
615
638
  } catch (schedErr) {
616
639
  // Non-fatal: task was created, just the scheduler event failed
@@ -5776,23 +5799,22 @@ function registerBrainExtractionHook(
5776
5799
  }
5777
5800
  }
5778
5801
 
5779
- // Phase 2: Parallel dedup check (1 query per candidate, all at once)
5802
+ // Phase 2: Single batch dedup check (1 query for ALL candidates)
5780
5803
  if (candidates.length > 0) {
5781
- const dedupResults = await Promise.all(
5782
- candidates.map(c =>
5783
- supabase.from("agent_memories")
5784
- .select("id")
5785
- .eq("user_id", userId)
5786
- .eq("agent_id", resolvedAgentId)
5787
- .eq("context_key", c.contextKey)
5788
- .is("superseded_by", null)
5789
- .limit(1)
5790
- )
5791
- );
5804
+ const allKeys = candidates.map(c => c.contextKey);
5805
+ const { data: existingRows } = await supabase
5806
+ .from("agent_memories")
5807
+ .select("context_key")
5808
+ .eq("user_id", userId)
5809
+ .eq("agent_id", resolvedAgentId)
5810
+ .in("context_key", allKeys)
5811
+ .is("superseded_by", null);
5812
+
5813
+ const existingKeys = new Set((existingRows || []).map((r: any) => r.context_key));
5792
5814
 
5793
5815
  // Phase 3: Batch insert only new candidates
5794
5816
  const toInsert = candidates
5795
- .filter((_, i) => !dedupResults[i].data?.length)
5817
+ .filter(c => !existingKeys.has(c.contextKey))
5796
5818
  .map(c => ({
5797
5819
  user_id: userId,
5798
5820
  agent_id: resolvedAgentId,
@@ -5835,6 +5857,51 @@ function registerBrainExtractionHook(
5835
5857
  // OpenClaw on every prompt build, resolves the correct agent, and loads
5836
5858
  // that specific agent's brain. Each agent gets its own memories.
5837
5859
 
5860
+ // Background cache refresher for stale-while-revalidate pattern
5861
+ async function refreshBrainCache(
5862
+ supabase: SupabaseClient,
5863
+ userId: string,
5864
+ agentId: string,
5865
+ api: any,
5866
+ ): Promise<void> {
5867
+ try {
5868
+ const now = new Date().toISOString();
5869
+ const { data: allBrain } = await supabase
5870
+ .from("agent_memories")
5871
+ .select("content, importance, tier")
5872
+ .eq("user_id", userId)
5873
+ .eq("agent_id", agentId)
5874
+ .in("tier", ["L1_focus", "L4_rule", "L5_persona"])
5875
+ .gt("decay_score", 0.1)
5876
+ .is("superseded_by", null)
5877
+ .or(`expires_at.is.null,expires_at.gt.${now}`)
5878
+ .order("importance", { ascending: false })
5879
+ .limit(18);
5880
+
5881
+ const all = allBrain || [];
5882
+ const l1 = all.filter((m: any) => m.tier === "L1_focus").slice(0, 5);
5883
+ const l4 = all.filter((m: any) => m.tier === "L4_rule").slice(0, 10);
5884
+ const l5 = all.filter((m: any) => m.tier === "L5_persona").slice(0, 3);
5885
+
5886
+ if (l1.length === 0 && l4.length === 0 && l5.length === 0) {
5887
+ brainCache.set(agentId, { text: "", at: Date.now() });
5888
+ return;
5889
+ }
5890
+
5891
+ const sections: string[] = [];
5892
+ if (l5.length > 0) sections.push("### Identity (L5_persona)\n" + l5.map((m: any) => `- ${m.content}`).join("\n"));
5893
+ if (l4.length > 0) sections.push("### ⚠️ Guardrails (L4_rule — DO NOT violate)\n" + l4.map((m: any) => `- ${m.content}`).join("\n"));
5894
+ if (l1.length > 0) sections.push("### Active Focus (L1_focus)\n" + l1.map((m: any) => `- ${m.content}`).join("\n"));
5895
+
5896
+ brainCache.set(agentId, {
5897
+ text: `<agent-brain>\n## Your Brain Context (TMT)\n\n${sections.join("\n\n")}\n</agent-brain>`,
5898
+ at: Date.now(),
5899
+ });
5900
+ } catch (e) {
5901
+ api.logger.debug?.(`[ofiere-brain] Background cache refresh error: ${e instanceof Error ? e.message : e}`);
5902
+ }
5903
+ }
5904
+
5838
5905
  function registerBrainContextHook(
5839
5906
  api: any,
5840
5907
  supabase: SupabaseClient,
@@ -5843,7 +5910,8 @@ function registerBrainContextHook(
5843
5910
  ): void {
5844
5911
  // Uses module-scoped brainCache (declared at top of file) for cross-function
5845
5912
  // invalidation. save_memory calls brainCache.delete(agentId) for immediate consistency.
5846
- const CACHE_TTL_MS = 300_000;
5913
+ const CACHE_TTL_MS = 600_000; // 10 min — brain context doesn't change fast
5914
+ const STALE_THRESHOLD_MS = 900_000; // 15 min — serve stale while refreshing
5847
5915
 
5848
5916
  try {
5849
5917
  api.on("before_prompt_build", async (_event: any, ctx: any) => {
@@ -5863,10 +5931,19 @@ function registerBrainContextHook(
5863
5931
 
5864
5932
  if (!resolvedAgentId) return;
5865
5933
 
5866
- // ── Check cache ──
5934
+ // ── Check cache (stale-while-revalidate) ──
5867
5935
  const cached = brainCache.get(resolvedAgentId);
5868
- if (cached && Date.now() - cached.at < CACHE_TTL_MS) {
5869
- return cached.text ? { appendSystemContext: cached.text } : undefined;
5936
+ if (cached) {
5937
+ const age = Date.now() - cached.at;
5938
+ if (age < CACHE_TTL_MS) {
5939
+ // Fresh — serve immediately
5940
+ return cached.text ? { appendSystemContext: cached.text } : undefined;
5941
+ }
5942
+ if (age < STALE_THRESHOLD_MS) {
5943
+ // Stale — serve stale and refresh in background (non-blocking)
5944
+ refreshBrainCache(supabase, userId, resolvedAgentId, api).catch(() => {});
5945
+ return cached.text ? { appendSystemContext: cached.text } : undefined;
5946
+ }
5870
5947
  }
5871
5948
 
5872
5949
  // ── Query brain memories for THIS specific agent ──