ofiere-openclaw-plugin 4.45.0 → 4.46.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.
- package/package.json +1 -1
- package/src/attachments.ts +39 -3
- package/src/staffPersona.ts +44 -0
- package/src/tools.ts +42 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.46.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Ofiere PM - 16 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, SOP management, agent brain, talent management, and corporate frameworks",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
package/src/attachments.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
invalidateAgentTier,
|
|
16
16
|
} from "./agent-tier.js";
|
|
17
17
|
import { issueAttachmentToken, verifyAttachmentToken } from "./attach-token.js";
|
|
18
|
-
import { loadSubagentRow, buildStaffPersonaBlock, readDispatchSubagentId, loadChiefStaffDefaults, resolveStaffModels, type SubagentRow } from "./staffPersona.js";
|
|
18
|
+
import { loadSubagentRow, buildStaffPersonaBlock, readDispatchSubagentId, loadChiefStaffDefaults, loadDispatchContextBySession, resolveStaffModels, type SubagentRow, type DispatchContextRow } from "./staffPersona.js";
|
|
19
19
|
|
|
20
20
|
interface ToolResult {
|
|
21
21
|
content: Array<{ type: "text"; text: string }>;
|
|
@@ -427,7 +427,27 @@ export function registerAttachmentContextHook(args: {
|
|
|
427
427
|
// Cycle 7b — staff persona injection. Only fires when subagent_id is
|
|
428
428
|
// present in dispatch metadata (task-dispatcher / scheduler / explicit
|
|
429
429
|
// dispatch params). Plain user chats with the chief never persona-swap.
|
|
430
|
-
|
|
430
|
+
//
|
|
431
|
+
// Cycle 7b BUGSHOOT-1 (BUG 2) — OpenClaw gateway core rejects unknown
|
|
432
|
+
// root key `metadata` on chat.send, so the dispatcher can't ride
|
|
433
|
+
// metadata through the wire. After the metadata path comes up empty,
|
|
434
|
+
// try the dispatch_context table keyed by sessionKey. The metadata
|
|
435
|
+
// path is preferred so a future gateway upgrade keeps working.
|
|
436
|
+
const sessionKey: string | null =
|
|
437
|
+
ctx?.sessionKey || ctx?.session_key ||
|
|
438
|
+
(typeof _event === "object" ? (_event?.sessionKey || _event?.context?.sessionKey || null) : null) ||
|
|
439
|
+
null;
|
|
440
|
+
let dispatchCtxRow: DispatchContextRow | null = null;
|
|
441
|
+
let subagentId = readDispatchSubagentId(ctx);
|
|
442
|
+
if (!subagentId && sessionKey) {
|
|
443
|
+
dispatchCtxRow = await loadDispatchContextBySession(supabase, userId, sessionKey).catch(() => null);
|
|
444
|
+
if (dispatchCtxRow?.subagent_id) {
|
|
445
|
+
subagentId = dispatchCtxRow.subagent_id;
|
|
446
|
+
api.logger?.debug?.(
|
|
447
|
+
`[ofiere-staff] subagent_id resolved via dispatch_context (session=${sessionKey})`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
431
451
|
let staffPrefix = "";
|
|
432
452
|
let staffRow: SubagentRow | null = null;
|
|
433
453
|
if (subagentId) {
|
|
@@ -460,7 +480,23 @@ export function registerAttachmentContextHook(args: {
|
|
|
460
480
|
// can stash explicit `attached_sop_ids` / `attached_framework_ids` on the
|
|
461
481
|
// chat.send frame's metadata. When present, prefer them over the
|
|
462
482
|
// most-recent-conversation lookup and bypass cache (per-dispatch ids).
|
|
463
|
-
|
|
483
|
+
let dispatchIds = readDispatchAttachmentIds(ctx);
|
|
484
|
+
// Cycle 7b BUGSHOOT-1 (BUG 2) — DB-backed dispatch-context fallback for
|
|
485
|
+
// attachment ids. Same rationale as the subagent_id fallback above.
|
|
486
|
+
if (!dispatchIds.sopIds.length && !dispatchIds.fwIds.length && sessionKey) {
|
|
487
|
+
if (!dispatchCtxRow) {
|
|
488
|
+
dispatchCtxRow = await loadDispatchContextBySession(supabase, userId, sessionKey).catch(() => null);
|
|
489
|
+
}
|
|
490
|
+
if (dispatchCtxRow && (dispatchCtxRow.attached_sop_ids.length || dispatchCtxRow.attached_framework_ids.length)) {
|
|
491
|
+
dispatchIds = {
|
|
492
|
+
sopIds: dispatchCtxRow.attached_sop_ids,
|
|
493
|
+
fwIds: dispatchCtxRow.attached_framework_ids,
|
|
494
|
+
};
|
|
495
|
+
api.logger?.debug?.(
|
|
496
|
+
`[ofiere-attach] dispatch ids via dispatch_context (session=${sessionKey}) sops=${dispatchIds.sopIds.length} fws=${dispatchIds.fwIds.length}`,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
464
500
|
if (dispatchIds.sopIds.length || dispatchIds.fwIds.length) {
|
|
465
501
|
api.logger?.debug?.(
|
|
466
502
|
`[ofiere-attach] dispatch ids agent=${resolvedAgentId} sops=${dispatchIds.sopIds.length} fws=${dispatchIds.fwIds.length}`,
|
package/src/staffPersona.ts
CHANGED
|
@@ -59,6 +59,50 @@ export function readDispatchSubagentId(ctx: any, event?: any): string | null {
|
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// Cycle 7b BUGSHOOT-1 (BUG 2) — DB-backed dispatch context fallback.
|
|
63
|
+
// OpenClaw gateway core rejects unknown root key `metadata` on chat.send, so
|
|
64
|
+
// the task-dispatcher edge function can't ride dispatch metadata through the
|
|
65
|
+
// wire. The dispatcher writes a row to public.dispatch_context keyed by
|
|
66
|
+
// (user_id, session_key) before chat.send fires; this loader reads it back
|
|
67
|
+
// during before_prompt_build when the metadata path comes up empty.
|
|
68
|
+
//
|
|
69
|
+
// Returns the dispatch context for the most recent matching row, or null if
|
|
70
|
+
// none exists. The metadata path is preferred so a future gateway upgrade
|
|
71
|
+
// that natively accepts metadata keeps working with no flag day.
|
|
72
|
+
export interface DispatchContextRow {
|
|
73
|
+
subagent_id: string | null;
|
|
74
|
+
attached_sop_ids: string[];
|
|
75
|
+
attached_framework_ids: string[];
|
|
76
|
+
task_id: string | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function loadDispatchContextBySession(
|
|
80
|
+
supabase: SupabaseClient,
|
|
81
|
+
userId: string,
|
|
82
|
+
sessionKey: string,
|
|
83
|
+
): Promise<DispatchContextRow | null> {
|
|
84
|
+
if (!sessionKey) return null;
|
|
85
|
+
const { data, error } = await supabase
|
|
86
|
+
.from("dispatch_context")
|
|
87
|
+
.select("subagent_id, attached_sop_ids, attached_framework_ids, task_id")
|
|
88
|
+
.eq("user_id", userId)
|
|
89
|
+
.eq("session_key", sessionKey)
|
|
90
|
+
.order("created_at", { ascending: false })
|
|
91
|
+
.limit(1)
|
|
92
|
+
.maybeSingle();
|
|
93
|
+
if (error || !data) return null;
|
|
94
|
+
return {
|
|
95
|
+
subagent_id: (data.subagent_id as string | null) ?? null,
|
|
96
|
+
attached_sop_ids: Array.isArray(data.attached_sop_ids)
|
|
97
|
+
? (data.attached_sop_ids as unknown[]).filter((x): x is string => typeof x === "string")
|
|
98
|
+
: [],
|
|
99
|
+
attached_framework_ids: Array.isArray(data.attached_framework_ids)
|
|
100
|
+
? (data.attached_framework_ids as unknown[]).filter((x): x is string => typeof x === "string")
|
|
101
|
+
: [],
|
|
102
|
+
task_id: (data.task_id as string | null) ?? null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
export type StaffModelSlot =
|
|
63
107
|
| "primary_model"
|
|
64
108
|
| "coding_model"
|
package/src/tools.ts
CHANGED
|
@@ -653,6 +653,12 @@ async function handleCreateTask(
|
|
|
653
653
|
if (!isNaN(parsedDate.getTime())) {
|
|
654
654
|
// Valid date — check if it includes a meaningful time component
|
|
655
655
|
const hasTimeInfo = /[T ]\d{2}:\d{2}/.test(startDate);
|
|
656
|
+
// BUG 4 fix (cycle 7b BUGSHOOT-1): if the input string already carries
|
|
657
|
+
// an explicit UTC marker (Z, +00, +0000, +00:00, UTC), the time
|
|
658
|
+
// component is NOT user-local — skip the local→UTC subtraction.
|
|
659
|
+
// Without this, a UTC-Z input like "2026-05-10T16:25:00Z" got mistaken
|
|
660
|
+
// for local 16:25 and stored as 09:25Z (off by TZ_OFFSET_HOURS).
|
|
661
|
+
const isUtcInput = /Z$|[+-]00:?00$|UTC$/i.test(startDate.trim());
|
|
656
662
|
|
|
657
663
|
if (explicitScheduledTime) {
|
|
658
664
|
// Agent explicitly passed a scheduled_time — treat as user’s local time
|
|
@@ -666,6 +672,15 @@ async function handleCreateTask(
|
|
|
666
672
|
scheduledDateFinal = dateStr;
|
|
667
673
|
// Normalize start_date to ISO UTC for consistent downstream parsing
|
|
668
674
|
insertData.start_date = dt.toISOString();
|
|
675
|
+
} else if (isUtcInput) {
|
|
676
|
+
// Input is already UTC — no local→UTC conversion. Use parsedDate
|
|
677
|
+
// directly. scheduled_time is documented as user-local, so add the
|
|
678
|
+
// offset to recover the local-clock value.
|
|
679
|
+
nextRunAtEpoch = Math.floor(parsedDate.getTime() / 1000);
|
|
680
|
+
const localHour = (parsedDate.getUTCHours() + TZ_OFFSET_HOURS + 24) % 24;
|
|
681
|
+
scheduledTimeFinal = `${String(localHour).padStart(2, "0")}:${String(parsedDate.getUTCMinutes()).padStart(2, "0")}`;
|
|
682
|
+
scheduledDateFinal = parsedDate.toISOString().split("T")[0];
|
|
683
|
+
insertData.start_date = parsedDate.toISOString();
|
|
669
684
|
} else if (hasTimeInfo) {
|
|
670
685
|
// start_date already contains time — assume it’s in the user’s local timezone
|
|
671
686
|
// Extract the local hour:minute from the string, NOT from UTC parsing
|
|
@@ -5387,14 +5402,36 @@ async function handleCreateSubagent(supabase: SupabaseClient, userId: string, pa
|
|
|
5387
5402
|
return err(`Maximum ${MAX_SUBAGENTS_PER_CHIEF} subagents per department chief`);
|
|
5388
5403
|
}
|
|
5389
5404
|
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5405
|
+
// BUG 1 fix (cycle 7b BUGSHOOT-1): persist persona/model/MCP fields on
|
|
5406
|
+
// create. Schema accepts them (see SUBAGENT_INPUT_SCHEMA above) but the
|
|
5407
|
+
// previous insert dropped everything except identity/UI fields, forcing a
|
|
5408
|
+
// follow-up update_subagent. Mirror the field whitelist used in
|
|
5409
|
+
// handleUpdateSubagent so create/update stay in lockstep.
|
|
5410
|
+
const insertRow: Record<string, unknown> = {
|
|
5411
|
+
user_id: userId,
|
|
5412
|
+
chief_agent_id: chiefAgentId,
|
|
5413
|
+
name,
|
|
5414
|
+
role: (params.role as string) || "Staff",
|
|
5415
|
+
codename: (params.codename as string) ?? null,
|
|
5416
|
+
color_hex: (params.color_hex as string) || "#64748b",
|
|
5417
|
+
};
|
|
5418
|
+
const personaFields = [
|
|
5419
|
+
"system_prompt", "mission", "responsibilities", "instructions",
|
|
5420
|
+
"primary_model", "coding_model", "tool_call_model", "function_call_model", "vision_model",
|
|
5421
|
+
] as const;
|
|
5422
|
+
for (const key of personaFields) {
|
|
5423
|
+
if (key in params && params[key] !== undefined) insertRow[key] = params[key];
|
|
5424
|
+
}
|
|
5425
|
+
if ("mcp_server_ids" in params && Array.isArray(params.mcp_server_ids)) {
|
|
5426
|
+
insertRow.mcp_server_ids = (params.mcp_server_ids as unknown[]).filter(
|
|
5427
|
+
(x): x is string => typeof x === "string",
|
|
5428
|
+
);
|
|
5429
|
+
}
|
|
5393
5430
|
|
|
5394
5431
|
const { data, error } = await supabase
|
|
5395
5432
|
.from("agent_subagents")
|
|
5396
|
-
.insert(
|
|
5397
|
-
.select(
|
|
5433
|
+
.insert(insertRow)
|
|
5434
|
+
.select()
|
|
5398
5435
|
.single();
|
|
5399
5436
|
if (error) return err(error.message);
|
|
5400
5437
|
return ok({ subagent: data, message: `Subagent "${name}" created under chief ${chiefAgentId}` });
|