@springbrand/message-panel 0.3.0-alpha.1 → 0.3.0-alpha.2
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/cloud-os/chat/cloud-os-chat-messages.tsx +4 -0
- package/cloud-os/chat/rich-blocks.tsx +85 -76
- package/cloud-os/chat/tool-presentation.ts +2 -2
- package/cloud-os/chat/transcript-model.ts +1 -1
- package/cloud-os/index.ts +2 -0
- package/package.json +1 -1
- package/src/camel/camel-tool-presentation.tsx +3 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScheduleCardControls } from "./rich-blocks";
|
|
1
2
|
import type { UIMessage } from "ai";
|
|
2
3
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
3
4
|
import {
|
|
@@ -117,6 +118,7 @@ export interface CloudOsChatMessagesProps {
|
|
|
117
118
|
*/
|
|
118
119
|
onRespond?: (toolCallId: string, response: unknown) => Promise<boolean>;
|
|
119
120
|
/** 点击定时任务卡片:宿主决定跳到哪(缺省 → 卡片不可点)。 */
|
|
121
|
+
scheduleControls?: ScheduleCardControls;
|
|
120
122
|
onOpenSchedule?: (scheduleId: string) => void;
|
|
121
123
|
onCopy?: (text: string) => void;
|
|
122
124
|
}
|
|
@@ -393,6 +395,7 @@ export function CloudOsChatMessages({
|
|
|
393
395
|
onSuggestion,
|
|
394
396
|
onRespond,
|
|
395
397
|
onOpenSchedule,
|
|
398
|
+
scheduleControls,
|
|
396
399
|
onOpenArtifact,
|
|
397
400
|
onCopy,
|
|
398
401
|
}: CloudOsChatMessagesProps) {
|
|
@@ -840,6 +843,7 @@ export function CloudOsChatMessages({
|
|
|
840
843
|
key={block.key}
|
|
841
844
|
call={block.call}
|
|
842
845
|
onOpen={onOpenSchedule}
|
|
846
|
+
controls={scheduleControls}
|
|
843
847
|
/>
|
|
844
848
|
);
|
|
845
849
|
case "approval": {
|
|
@@ -708,6 +708,14 @@ function scheduleCadence(input: Record<string, unknown>): string {
|
|
|
708
708
|
} else if (trigger.kind === "cron" && typeof trigger.cron === "string") {
|
|
709
709
|
cadence = `Cron ${trigger.cron}`;
|
|
710
710
|
}
|
|
711
|
+
if (trigger.kind === "local" && typeof trigger.rule === "object" && trigger.rule) {
|
|
712
|
+
const rule = trigger.rule as Record<string, unknown>;
|
|
713
|
+
cadence = rule.kind === "once" ? `Once ${rule.date} at ${rule.time}`
|
|
714
|
+
: rule.kind === "weekly" ? `Weekly (${Array.isArray(rule.weekdays) ? rule.weekdays.map(day => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][Number(day)]).join(", ") : ""}) at ${rule.time}`
|
|
715
|
+
: `Daily at ${rule.time}`;
|
|
716
|
+
if (trigger.startDate) cadence += ` from ${trigger.startDate}`;
|
|
717
|
+
if (trigger.endDate) cadence += ` through ${trigger.endDate}`;
|
|
718
|
+
}
|
|
711
719
|
if (!cadence) cadence = String(input.cadence ?? input.schedule ?? "Scheduled");
|
|
712
720
|
const timezone =
|
|
713
721
|
typeof input.timezone === "string"
|
|
@@ -736,9 +744,11 @@ export function ScheduleConfirmation({
|
|
|
736
744
|
onCancel: () => void;
|
|
737
745
|
onConfirm: () => void;
|
|
738
746
|
}) {
|
|
747
|
+
const authorization = input.authorization as { scope?: string; missing?: string[] } | undefined;
|
|
748
|
+
const missing = authorization?.missing ?? [];
|
|
739
749
|
const name = String(input.label ?? input.title ?? "Scheduled task");
|
|
740
750
|
const prompt = typeof input.prompt === "string" && input.prompt.trim()
|
|
741
|
-
? input.prompt
|
|
751
|
+
? input.prompt
|
|
742
752
|
: "Run the scheduled task";
|
|
743
753
|
|
|
744
754
|
return (
|
|
@@ -755,10 +765,10 @@ export function ScheduleConfirmation({
|
|
|
755
765
|
<Clock size={14} />
|
|
756
766
|
</span>
|
|
757
767
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
758
|
-
<span className="
|
|
768
|
+
<span className="break-words text-[13.5px] leading-[18px] font-medium">
|
|
759
769
|
{name}
|
|
760
770
|
</span>
|
|
761
|
-
<span className="
|
|
771
|
+
<span className="break-words font-mono text-[11px] leading-4 tracking-tight text-kumo-inactive">
|
|
762
772
|
{scheduleCadence(input)}
|
|
763
773
|
</span>
|
|
764
774
|
</div>
|
|
@@ -768,16 +778,20 @@ export function ScheduleConfirmation({
|
|
|
768
778
|
<span className="shrink-0 font-mono text-[11px] leading-[18px] tracking-tight text-kumo-inactive">
|
|
769
779
|
task
|
|
770
780
|
</span>
|
|
771
|
-
<span className="min-w-0 flex-1 text-[13px] leading-[18px] text-kumo-subtle">
|
|
781
|
+
<span className="min-w-0 flex-1 whitespace-pre-wrap text-[13px] leading-[18px] text-kumo-subtle">
|
|
772
782
|
{prompt}
|
|
773
783
|
</span>
|
|
774
784
|
</div>
|
|
775
785
|
|
|
786
|
+
{input.id != null && <p className="m-0 text-xs">Task ID: {String(input.id)}</p>}
|
|
787
|
+
{typeof input.nextRunAt === "number" && <p className="m-0 text-xs">Next run: {new Intl.DateTimeFormat("en", { dateStyle: "medium", timeStyle: "short", timeZone: String(input.timezone) }).format(input.nextRunAt)}</p>}
|
|
788
|
+
{authorization?.scope && <div className="text-sm"><strong>Ongoing execution scope</strong><p className="m-0 whitespace-pre-wrap">{authorization.scope}</p></div>}
|
|
789
|
+
{missing.length > 0 && <div role="alert" className="text-sm text-kumo-danger">{missing.map((item, i) => <p key={i}>{item}</p>)}</div>}
|
|
776
790
|
<div className="flex items-center justify-end gap-2">
|
|
777
791
|
<WorkshopButton disabled={disabled} onClick={onCancel}>
|
|
778
792
|
Cancel
|
|
779
793
|
</WorkshopButton>
|
|
780
|
-
<WorkshopButton tone="primary" disabled={disabled} onClick={onConfirm}>
|
|
794
|
+
<WorkshopButton tone="primary" disabled={disabled || missing.length > 0} onClick={onConfirm}>
|
|
781
795
|
{disabled ? "Confirming…" : "Confirm schedule"}
|
|
782
796
|
</WorkshopButton>
|
|
783
797
|
</div>
|
|
@@ -785,81 +799,76 @@ export function ScheduleConfirmation({
|
|
|
785
799
|
);
|
|
786
800
|
}
|
|
787
801
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
802
|
+
/** Current task facts are supplied by the Host, never inferred from a past Tool result. */
|
|
803
|
+
export interface ScheduleCardState {
|
|
804
|
+
id: string;
|
|
805
|
+
enabled: boolean;
|
|
806
|
+
status: string;
|
|
807
|
+
deleted: boolean;
|
|
808
|
+
canToggle?: boolean;
|
|
809
|
+
}
|
|
810
|
+
export interface ScheduleCardControls {
|
|
811
|
+
load(id: string): Promise<ScheduleCardState>;
|
|
812
|
+
setEnabled(id: string, enabled: boolean): Promise<ScheduleCardState>;
|
|
813
|
+
open(id: string): void;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export function ScheduleBlock({ call, onOpen, controls }: {
|
|
792
817
|
call: CloudOsToolCall;
|
|
793
|
-
/** 有 id 且宿主给了回调时,整张卡变成跳到「已安排」详情的按钮。 */
|
|
794
818
|
onOpen?: (scheduleId: string) => void;
|
|
819
|
+
controls?: ScheduleCardControls;
|
|
795
820
|
}) {
|
|
796
|
-
|
|
821
|
+
const scheduleId = !call.failed && !call.running ? scheduleIdOf(call.output) : "";
|
|
822
|
+
const [current, setCurrent] = useState<ScheduleCardState>();
|
|
823
|
+
const [busy, setBusy] = useState(false);
|
|
824
|
+
const [error, setError] = useState("");
|
|
825
|
+
const generation = useRef(0);
|
|
826
|
+
useEffect(() => {
|
|
827
|
+
const token = ++generation.current;
|
|
828
|
+
setCurrent(undefined); setError(""); setBusy(false);
|
|
829
|
+
if (scheduleId && controls) {
|
|
830
|
+
controls.load(scheduleId).then(value => {
|
|
831
|
+
if (token !== generation.current) return;
|
|
832
|
+
if (value.id !== scheduleId) throw new Error("Host returned a different task");
|
|
833
|
+
setCurrent(value);
|
|
834
|
+
}).catch(cause => { if (token === generation.current) setError(String(cause instanceof Error ? cause.message : cause)); });
|
|
835
|
+
}
|
|
836
|
+
return () => { generation.current++; };
|
|
837
|
+
}, [scheduleId, controls]);
|
|
797
838
|
if (call.awaitingApproval) return null;
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
</p>
|
|
832
|
-
</div>
|
|
833
|
-
<span
|
|
834
|
-
className={`ml-auto flex flex-shrink-0 items-center gap-1 self-center text-[12px] leading-4 font-medium ${
|
|
835
|
-
state === "failed"
|
|
836
|
-
? "text-kumo-danger"
|
|
837
|
-
: state === "running"
|
|
838
|
-
? "text-kumo-brand"
|
|
839
|
-
: "text-kumo-success"
|
|
840
|
-
}`}
|
|
841
|
-
>
|
|
842
|
-
{state === "failed" ? (
|
|
843
|
-
<WarningCircle size={13} weight="fill" />
|
|
844
|
-
) : state === "running" ? (
|
|
845
|
-
<CircleNotch
|
|
846
|
-
size={13}
|
|
847
|
-
weight="bold"
|
|
848
|
-
className="animate-spin motion-reduce:animate-none"
|
|
849
|
-
/>
|
|
850
|
-
) : (
|
|
851
|
-
<Check size={13} weight="bold" />
|
|
852
|
-
)}
|
|
853
|
-
{state === "failed"
|
|
854
|
-
? "Failed"
|
|
855
|
-
: state === "running"
|
|
856
|
-
? "Scheduling…"
|
|
857
|
-
: "Scheduled"}
|
|
858
|
-
</span>
|
|
859
|
-
</div>
|
|
860
|
-
</Card>
|
|
861
|
-
</div>
|
|
862
|
-
);
|
|
839
|
+
const saved = (call.output as { task?: Record<string, unknown> } | undefined)?.task;
|
|
840
|
+
const task = saved ?? call.input;
|
|
841
|
+
const name = String(task.label ?? task.title ?? "Scheduled task");
|
|
842
|
+
const open = controls ? (current && !current.deleted ? controls.open : undefined) : onOpen;
|
|
843
|
+
const clickable = Boolean(scheduleId && open);
|
|
844
|
+
const Card = clickable && !controls ? "button" : "div";
|
|
845
|
+
const toggle = async () => {
|
|
846
|
+
if (!controls || !current || current.deleted || current.canToggle === false || busy) return;
|
|
847
|
+
const token = generation.current;
|
|
848
|
+
setBusy(true); setError("");
|
|
849
|
+
try {
|
|
850
|
+
const value = await controls.setEnabled(scheduleId, !current.enabled);
|
|
851
|
+
if (value.id !== scheduleId) throw new Error("Host returned a different task");
|
|
852
|
+
if (token === generation.current) setCurrent(value);
|
|
853
|
+
} catch (cause) {
|
|
854
|
+
if (token === generation.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
855
|
+
} finally { if (token === generation.current) setBusy(false); }
|
|
856
|
+
};
|
|
857
|
+
return <div className="max-w-[860px]">
|
|
858
|
+
<Card className="w-full rounded-2xl border border-kumo-line bg-kumo-base px-4 py-3 text-left"
|
|
859
|
+
{...(clickable && !controls ? { type: "button" as const, onClick: () => open?.(scheduleId), "aria-label": `Open scheduled task: ${name}` } : {})}>
|
|
860
|
+
<div className="flex items-center gap-3"><Bell size={18} /><span className="min-w-0 flex-1 break-words text-sm font-medium">{name}</span>
|
|
861
|
+
<span className="text-xs">{call.failed ? "Failed" : call.running ? "Scheduling…" : "Scheduled"}</span>
|
|
862
|
+
</div>
|
|
863
|
+
<p className="my-1 text-xs text-kumo-inactive">{scheduleCadence(task)}</p>
|
|
864
|
+
{controls && scheduleId && <div className="flex items-center gap-3 text-sm">
|
|
865
|
+
<span>{current?.status ?? (error ? "Status unavailable" : "Loading…")}</span>
|
|
866
|
+
{current && !current.deleted && <button type="button" role="switch" aria-label="Enable scheduled task" aria-checked={current.enabled} disabled={busy || current.canToggle === false} onClick={() => void toggle()}>{current.enabled ? "On" : "Off"}</button>}
|
|
867
|
+
{clickable && <button type="button" aria-label={`Open scheduled task: ${name}`} onClick={() => open?.(scheduleId)}>Scheduled</button>}
|
|
868
|
+
</div>}
|
|
869
|
+
{error && <p role="alert" className="text-xs text-kumo-danger">{error}</p>}
|
|
870
|
+
</Card>
|
|
871
|
+
</div>;
|
|
863
872
|
}
|
|
864
873
|
|
|
865
874
|
// ── 原位授权 ──────────────────────────────────────────────
|
|
@@ -241,7 +241,7 @@ export function canonicalToolKind(name: string): CloudOsToolKind {
|
|
|
241
241
|
return "ask-user";
|
|
242
242
|
}
|
|
243
243
|
if (["suggest_followups", "suggestions"].includes(normalized)) return "suggestions";
|
|
244
|
-
if (["schedule", "cancel_schedule"].includes(normalized)) return "schedule";
|
|
244
|
+
if (["schedule", "update_schedule", "pause_schedule", "resume_schedule", "cancel_schedule", "list_schedules", "change_schedule_agent"].includes(normalized)) return "schedule";
|
|
245
245
|
if (normalized === "create_agent") return "create-agent";
|
|
246
246
|
if (normalized === "publish_extension") return "publish-extension";
|
|
247
247
|
return "generic";
|
|
@@ -353,7 +353,7 @@ export function getToolCallSummary(call: CloudOsToolCall): {
|
|
|
353
353
|
case "schedule": {
|
|
354
354
|
const label = stringField(input, "label", "title");
|
|
355
355
|
return {
|
|
356
|
-
verb: toolName === "cancel_schedule" ? "Cancelled schedule" : "Scheduled",
|
|
356
|
+
verb: toolName === "cancel_schedule" ? "Cancelled schedule" : toolName === "update_schedule" ? "Updated schedule" : toolName === "pause_schedule" ? "Paused schedule" : toolName === "resume_schedule" ? "Resumed schedule" : toolName === "list_schedules" ? "Listed schedules" : toolName === "change_schedule_agent" ? "Changed schedule Agent" : "Scheduled",
|
|
357
357
|
target: label || undefined,
|
|
358
358
|
};
|
|
359
359
|
}
|
|
@@ -594,7 +594,7 @@ function assistantBlocks(
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
if (call.kind === "schedule" && call.toolName === "schedule") {
|
|
597
|
+
if (call.kind === "schedule" && (call.toolName === "schedule" || call.toolName === "update_schedule")) {
|
|
598
598
|
flush();
|
|
599
599
|
blocks.push({ kind: "schedule", key, call });
|
|
600
600
|
return;
|
package/cloud-os/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -544,6 +544,9 @@ function summaryFor(
|
|
|
544
544
|
? `Could not publish ${extension}`
|
|
545
545
|
: `Published ${extension}`;
|
|
546
546
|
}
|
|
547
|
+
if (name === "schedule" || name === "update_schedule") {
|
|
548
|
+
return running ? "Preparing schedule..." : failed ? "Schedule was not saved" : name === "schedule" ? "Scheduled task" : "Updated schedule";
|
|
549
|
+
}
|
|
547
550
|
if (name === "cancel_schedule") {
|
|
548
551
|
return running
|
|
549
552
|
? "Cancelling schedule..."
|