@agentprojectcontext/apx 1.78.0 โ 1.79.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/core/agent/run-agent.js +30 -5
- package/src/core/agent/tool-summary.js +65 -0
- package/src/core/agent/tools/handlers/list-commitments.js +80 -0
- package/src/core/agent/tools/handlers/list-tasks.js +66 -27
- package/src/core/agent/tools/handlers/record-commitment.js +68 -0
- package/src/core/agent/tools/handlers/send-telegram.js +68 -2
- package/src/core/agent/tools/names.js +6 -0
- package/src/core/agent/tools/registry.js +9 -0
- package/src/core/agent/tools/tool-call-parser.js +70 -1
- package/src/core/channels/telegram/ask-callbacks.js +35 -0
- package/src/core/channels/telegram/dispatch.js +3 -0
- package/src/core/channels/telegram/reply.js +30 -5
- package/src/core/config/paths.js +3 -0
- package/src/core/config/redact.js +22 -0
- package/src/core/daemon/service.js +238 -0
- package/src/core/engines/gemini.js +322 -60
- package/src/core/engines/openai-compatible.js +21 -2
- package/src/core/memory/consolidate.js +225 -0
- package/src/core/nudge/index.js +192 -0
- package/src/core/nudge/policy.js +143 -0
- package/src/core/nudge/store.js +141 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
- package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
- package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
- package/src/core/routines/runner.js +102 -3
- package/src/core/routines/signals.js +270 -0
- package/src/core/stores/commitments.js +331 -0
- package/src/core/stores/messages.js +4 -0
- package/src/core/stores/routines.js +17 -3
- package/src/core/util/thinking.js +51 -0
- package/src/host/daemon/api/commitments.js +135 -0
- package/src/host/daemon/api/nudges.js +112 -0
- package/src/host/daemon/api/routines.js +24 -0
- package/src/host/daemon/api/self-memory.js +50 -0
- package/src/host/daemon/api/telegram.js +42 -4
- package/src/host/daemon/api/voice.js +3 -1
- package/src/host/daemon/api.js +6 -0
- package/src/host/daemon/callback-reconciler.js +16 -0
- package/src/host/daemon/plugins/desktop/index.js +7 -1
- package/src/host/daemon/plugins/telegram/index.js +7 -2
- package/src/host/daemon/wakeup.js +17 -3
- package/src/interfaces/cli/commands/commitment.js +154 -0
- package/src/interfaces/cli/commands/daemon.js +57 -0
- package/src/interfaces/cli/commands/memory.js +73 -0
- package/src/interfaces/cli/commands/nudge.js +130 -0
- package/src/interfaces/cli/help/index.js +2 -2
- package/src/interfaces/cli/routes/commitment.js +19 -0
- package/src/interfaces/cli/routes/daemon.js +7 -1
- package/src/interfaces/cli/routes/index.js +4 -0
- package/src/interfaces/cli/routes/memory.js +10 -2
- package/src/interfaces/cli/routes/nudge.js +17 -0
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +849 -0
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +11 -10
- package/src/interfaces/web/src/components/Section.tsx +18 -3
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +13 -0
- package/src/interfaces/web/src/components/cron/CronPicker.tsx +196 -0
- package/src/interfaces/web/src/components/inbox/InboxList.tsx +145 -0
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +34 -4
- package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +12 -2
- package/src/interfaces/web/src/components/routines/shared.ts +14 -5
- package/src/interfaces/web/src/components/settings/NudgePanel.tsx +183 -0
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
- package/src/interfaces/web/src/components/ui/filter-chips.tsx +47 -0
- package/src/interfaces/web/src/components/ui.tsx +1 -0
- package/src/interfaces/web/src/constants/index.ts +1 -0
- package/src/interfaces/web/src/hooks/useChat.ts +5 -1
- package/src/interfaces/web/src/hooks/useNudges.ts +38 -0
- package/src/interfaces/web/src/i18n/en.ts +127 -0
- package/src/interfaces/web/src/i18n/es.ts +127 -0
- package/src/interfaces/web/src/lib/api/commitments.ts +57 -0
- package/src/interfaces/web/src/lib/api/notebook.ts +23 -0
- package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
- package/src/interfaces/web/src/lib/cron.ts +196 -0
- package/src/interfaces/web/src/lib/when.ts +32 -0
- package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
- package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +102 -19
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +15 -0
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +21 -3
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +13 -11
- package/src/interfaces/web/src/types/daemon.ts +10 -1
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js +0 -824
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +0 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { ThumbsDown, ThumbsUp, Info } from "lucide-react";
|
|
3
|
+
import { Section } from "../Section";
|
|
4
|
+
import { Badge, Button, Empty, Field, Input, Loading, Switch } from "../ui";
|
|
5
|
+
import { useToast } from "../Toast";
|
|
6
|
+
import { useNudges, useNudgePolicy } from "../../hooks/useNudges";
|
|
7
|
+
import { NudgesApi, type NudgePolicy } from "../../lib/api/nudges";
|
|
8
|
+
import { t } from "../../i18n";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The interruption budget โ how often APX may speak unprompted.
|
|
12
|
+
*
|
|
13
|
+
* Two halves that belong together: the rule, and the record of what the rule
|
|
14
|
+
* let through. Showing the ledger next to the dial is the point โ someone
|
|
15
|
+
* deciding "is three a day too many?" needs to see the three.
|
|
16
|
+
*/
|
|
17
|
+
export function NudgePanel() {
|
|
18
|
+
const toast = useToast();
|
|
19
|
+
const { policy, source, isLoading, mutate } = useNudgePolicy();
|
|
20
|
+
const { entries, stats, mutate: mutateLedger } = useNudges(30);
|
|
21
|
+
|
|
22
|
+
const [draft, setDraft] = useState<NudgePolicy | null>(null);
|
|
23
|
+
const [busy, setBusy] = useState(false);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (policy) setDraft({ ...policy });
|
|
27
|
+
}, [policy]);
|
|
28
|
+
|
|
29
|
+
if (isLoading || !draft) return <Loading />;
|
|
30
|
+
|
|
31
|
+
const save = async () => {
|
|
32
|
+
setBusy(true);
|
|
33
|
+
try {
|
|
34
|
+
await NudgesApi.setPolicy(draft);
|
|
35
|
+
await mutate();
|
|
36
|
+
toast.success(t("settings.nudge.saved"));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
toast.error((e as Error).message);
|
|
39
|
+
} finally {
|
|
40
|
+
setBusy(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const rate = async (id: string, useful: boolean) => {
|
|
45
|
+
try {
|
|
46
|
+
await NudgesApi.feedback(id, useful);
|
|
47
|
+
await mutateLedger();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
toast.error((e as Error).message);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const num = (key: keyof NudgePolicy, label: string, hint?: string) => (
|
|
54
|
+
<Field label={label} hint={hint}>
|
|
55
|
+
<Input
|
|
56
|
+
type="number"
|
|
57
|
+
min={0}
|
|
58
|
+
value={String(draft[key] ?? 0)}
|
|
59
|
+
disabled={!draft.enabled}
|
|
60
|
+
onChange={(e) => setDraft({ ...draft, [key]: Number(e.target.value) || 0 })}
|
|
61
|
+
/>
|
|
62
|
+
</Field>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const left = draft.daily_max > 0 ? Math.max(0, draft.daily_max - (stats?.today ?? 0)) : "โ";
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex flex-col gap-4" data-testid="nudge-panel">
|
|
69
|
+
<Section title={t("settings.nudge.title")} description={t("settings.nudge.subtitle")}>
|
|
70
|
+
<div className="mb-3 flex items-start gap-2 rounded-md border border-border bg-muted/40 p-3 text-sm">
|
|
71
|
+
<Info size={16} className="mt-0.5 shrink-0 opacity-70" />
|
|
72
|
+
<span>
|
|
73
|
+
{draft.enabled
|
|
74
|
+
? t("settings.nudge.on_hint")
|
|
75
|
+
.replace("{{sent}}", String(stats?.today ?? 0))
|
|
76
|
+
.replace("{{left}}", String(left))
|
|
77
|
+
: t("settings.nudge.off_hint")}
|
|
78
|
+
{source.length ? (
|
|
79
|
+
<span className="ml-1 opacity-60">
|
|
80
|
+
({t("settings.nudge.source")}: {source.join(" โ ")})
|
|
81
|
+
</span>
|
|
82
|
+
) : null}
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className="mb-4">
|
|
87
|
+
<Switch
|
|
88
|
+
checked={draft.enabled}
|
|
89
|
+
onChange={(v) => setDraft({ ...draft, enabled: v })}
|
|
90
|
+
label={t("settings.nudge.enabled")}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
95
|
+
{num("daily_max", t("settings.nudge.daily_max"), t("settings.nudge.daily_max_hint"))}
|
|
96
|
+
<Field label={t("settings.nudge.quiet_hours")} hint={t("settings.nudge.quiet_hours_hint")}>
|
|
97
|
+
<Input
|
|
98
|
+
value={draft.quiet_hours ?? ""}
|
|
99
|
+
placeholder="22:00-07:30"
|
|
100
|
+
disabled={!draft.enabled}
|
|
101
|
+
onChange={(e) => setDraft({ ...draft, quiet_hours: e.target.value })}
|
|
102
|
+
/>
|
|
103
|
+
</Field>
|
|
104
|
+
{num("cooldown_minutes", t("settings.nudge.cooldown"))}
|
|
105
|
+
{num("project_cooldown_minutes", t("settings.nudge.project_cooldown"))}
|
|
106
|
+
{num("kind_cooldown_minutes", t("settings.nudge.kind_cooldown"))}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div className="mt-3">
|
|
110
|
+
<Switch
|
|
111
|
+
checked={draft.critical_bypasses_budget}
|
|
112
|
+
onChange={(v) => setDraft({ ...draft, critical_bypasses_budget: v })}
|
|
113
|
+
label={t("settings.nudge.critical_bypass")}
|
|
114
|
+
disabled={!draft.enabled}
|
|
115
|
+
/>
|
|
116
|
+
<p className="mt-1 text-[11px] text-muted-foreground/70">
|
|
117
|
+
{t("settings.nudge.critical_bypass_hint")}
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div className="mt-4">
|
|
122
|
+
<Button variant="primary" loading={busy} onClick={save}>
|
|
123
|
+
{t("common.save")}
|
|
124
|
+
</Button>
|
|
125
|
+
</div>
|
|
126
|
+
</Section>
|
|
127
|
+
|
|
128
|
+
<Section title={t("settings.nudge.log_title")} description={t("settings.nudge.log_subtitle")}>
|
|
129
|
+
{!entries.length ? (
|
|
130
|
+
<Empty>{t("settings.nudge.log_empty")}</Empty>
|
|
131
|
+
) : (
|
|
132
|
+
<ul className="flex flex-col gap-2" data-testid="nudge-log">
|
|
133
|
+
{entries.map((e) => (
|
|
134
|
+
<li
|
|
135
|
+
key={e.id}
|
|
136
|
+
className="flex items-start gap-3 rounded-md border border-border bg-muted/20 p-2.5 text-sm"
|
|
137
|
+
>
|
|
138
|
+
<span className="min-w-0 flex-1">
|
|
139
|
+
<span className="flex flex-wrap items-center gap-2">
|
|
140
|
+
<Badge>{e.kind}</Badge>
|
|
141
|
+
<span className="text-xs opacity-55">
|
|
142
|
+
{String(e.at).replace("T", " ").slice(0, 16)}
|
|
143
|
+
</span>
|
|
144
|
+
{e.bypassed_budget ? (
|
|
145
|
+
<Badge tone="warning">{t("settings.nudge.bypass")}</Badge>
|
|
146
|
+
) : null}
|
|
147
|
+
</span>
|
|
148
|
+
<span className="mt-1 block line-clamp-2 opacity-80">{e.preview}</span>
|
|
149
|
+
</span>
|
|
150
|
+
|
|
151
|
+
{/* Rating is available for as long as the entry is on file, not
|
|
152
|
+
only in the chat where the buttons were. Retrospect is when
|
|
153
|
+
people actually notice a pattern was noise. */}
|
|
154
|
+
<span className="flex shrink-0 items-center gap-1">
|
|
155
|
+
<button
|
|
156
|
+
type="button"
|
|
157
|
+
aria-label={t("settings.nudge.useful")}
|
|
158
|
+
onClick={() => rate(e.id, true)}
|
|
159
|
+
className={`rounded p-1.5 transition hover:bg-muted ${
|
|
160
|
+
e.feedback?.useful === true ? "text-emerald-500" : "opacity-40"
|
|
161
|
+
}`}
|
|
162
|
+
>
|
|
163
|
+
<ThumbsUp size={14} />
|
|
164
|
+
</button>
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
aria-label={t("settings.nudge.noise")}
|
|
168
|
+
onClick={() => rate(e.id, false)}
|
|
169
|
+
className={`rounded p-1.5 transition hover:bg-muted ${
|
|
170
|
+
e.feedback?.useful === false ? "text-red-500" : "opacity-40"
|
|
171
|
+
}`}
|
|
172
|
+
>
|
|
173
|
+
<ThumbsDown size={14} />
|
|
174
|
+
</button>
|
|
175
|
+
</span>
|
|
176
|
+
</li>
|
|
177
|
+
))}
|
|
178
|
+
</ul>
|
|
179
|
+
)}
|
|
180
|
+
</Section>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import { AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
|
3
3
|
import { Section } from "../Section";
|
|
4
|
-
import { Badge, Button, Dialog, Empty, Field, Input, Loading } from "../ui";
|
|
4
|
+
import { Badge, Button, Dialog, Empty, Field, Input, Loading, Switch } from "../ui";
|
|
5
5
|
import { UiSelect } from "../UiSelect";
|
|
6
6
|
import { useToast } from "../Toast";
|
|
7
7
|
import { useProfiles, useProfile, useProfileDoctor } from "../../hooks/useProfiles";
|
|
8
8
|
import { ProfilesApi, type ProfileSchemaProp } from "../../lib/api/profiles";
|
|
9
|
+
import { CronPicker } from "../cron/CronPicker";
|
|
9
10
|
import { t } from "../../i18n";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -15,6 +16,11 @@ import { t } from "../../i18n";
|
|
|
15
16
|
* Identity), nor with configuration profiles. With none active, APX behaves
|
|
16
17
|
* exactly as it always has, and the panel says so rather than looking broken.
|
|
17
18
|
*/
|
|
19
|
+
/** Schema keys whose value is a five-field cron expression. */
|
|
20
|
+
function isCronKey(key: string): boolean {
|
|
21
|
+
return /(^|_)schedule$/.test(key) || /_cron$/.test(key);
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
export function ProfilePanel() {
|
|
19
25
|
const toast = useToast();
|
|
20
26
|
const { active, profiles, isLoading, mutate } = useProfiles();
|
|
@@ -143,16 +149,25 @@ export function ProfilePanel() {
|
|
|
143
149
|
)}
|
|
144
150
|
|
|
145
151
|
{profile ? (
|
|
146
|
-
<div className="mt-4 flex flex-wrap items-center gap-
|
|
147
|
-
{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
<div className="mt-4 flex flex-wrap items-center gap-3">
|
|
153
|
+
{/* A switch, like every other on/off in Settings (Engines, Skills,
|
|
154
|
+
Desktop autostart). The red "Deactivate" button read as a
|
|
155
|
+
destructive action โ deactivating a profile destroys nothing:
|
|
156
|
+
its settings survive and turning it back on restores them. */}
|
|
157
|
+
<Switch
|
|
158
|
+
checked={!!profile.active}
|
|
159
|
+
disabled={busy}
|
|
160
|
+
label={profile.active ? t("settings.profile.on") : t("settings.profile.off")}
|
|
161
|
+
onChange={(on) => {
|
|
162
|
+
if (on) activate(profile.id, !!active);
|
|
163
|
+
// Switching OFF still confirms: the profile's routines stop
|
|
164
|
+
// with it, and that is worth one deliberate tap.
|
|
165
|
+
else setConfirmOff(true);
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
{!profile.active && active ? (
|
|
169
|
+
<span className="text-xs opacity-60">{t("settings.profile.replaces_active")}</span>
|
|
170
|
+
) : null}
|
|
156
171
|
<span className="text-xs opacity-60">
|
|
157
172
|
{t("settings.profile.token_cost")}: ~{profile.tokens ?? 0}
|
|
158
173
|
{profile.budget ? ` / ${profile.budget}` : ""}
|
|
@@ -183,7 +198,16 @@ export function ProfilePanel() {
|
|
|
183
198
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
184
199
|
{Object.entries(props).map(([key, def]) => (
|
|
185
200
|
<Field key={key} label={def.title || key} hint={def.description}>
|
|
186
|
-
{
|
|
201
|
+
{/* A schedule is a time and some days, not five
|
|
202
|
+
space-separated fields. Detected by key name because the
|
|
203
|
+
JSON-Schema subset has no "format" for cron. */}
|
|
204
|
+
{isCronKey(key) ? (
|
|
205
|
+
<CronPicker
|
|
206
|
+
value={draft[key] ?? String(def.default ?? "")}
|
|
207
|
+
onChange={(expr) => setDraft({ ...draft, [key]: expr })}
|
|
208
|
+
disabled={!canEdit}
|
|
209
|
+
/>
|
|
210
|
+
) : def.enum ? (
|
|
187
211
|
<UiSelect
|
|
188
212
|
value={draft[key] ?? String(def.default ?? "")}
|
|
189
213
|
onChange={(v) => setDraft({ ...draft, [key]: v })}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One row of mutually-exclusive filters.
|
|
5
|
+
*
|
|
6
|
+
* Exists so every list page filters the same way and reads the same way.
|
|
7
|
+
* Labels are Capitalised: the chips used to render whatever the state value
|
|
8
|
+
* happened to be ("open", "in review"), which put raw storage vocabulary in
|
|
9
|
+
* the interface.
|
|
10
|
+
*/
|
|
11
|
+
export function FilterChips<T extends string>({
|
|
12
|
+
value,
|
|
13
|
+
options,
|
|
14
|
+
onChange,
|
|
15
|
+
label,
|
|
16
|
+
}: {
|
|
17
|
+
value: T;
|
|
18
|
+
options: { value: T; label: string }[];
|
|
19
|
+
onChange: (v: T) => void;
|
|
20
|
+
label?: string;
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex flex-wrap items-center gap-1" role="group" aria-label={label}>
|
|
24
|
+
{options.map((o) => (
|
|
25
|
+
<button
|
|
26
|
+
key={o.value}
|
|
27
|
+
type="button"
|
|
28
|
+
aria-pressed={value === o.value}
|
|
29
|
+
onClick={() => onChange(o.value)}
|
|
30
|
+
className={cn(
|
|
31
|
+
"rounded-md px-2.5 py-1 text-xs font-medium transition-colors",
|
|
32
|
+
value === o.value
|
|
33
|
+
? "bg-accent text-accent-fg"
|
|
34
|
+
: "text-muted-fg hover:bg-muted/50 hover:text-foreground",
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
{capitalise(o.label)}
|
|
38
|
+
</button>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** First letter up, rest untouched โ so "in review" keeps its lowercase w. */
|
|
45
|
+
function capitalise(s: string): string {
|
|
46
|
+
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
|
|
47
|
+
}
|
|
@@ -15,6 +15,7 @@ import { Dialog as DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogD
|
|
|
15
15
|
// Re-export the tooltip convenience wrapper so call sites can grab it from the
|
|
16
16
|
// same barrel as Button/Field/etc.: `import { Button, Tip } from "../ui"`.
|
|
17
17
|
export { Tip } from "./ui/tip";
|
|
18
|
+
export { FilterChips } from "./ui/filter-chips";
|
|
18
19
|
|
|
19
20
|
// โโ Button โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
20
21
|
type Variant = "primary" | "secondary" | "ghost" | "destructive";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
2
|
import { SuperAgent, Agents, Conversations } from "../lib/api";
|
|
3
|
-
import type { ChatStreamEvent, ChatUsage, ConversationMessage } from "../types/daemon";
|
|
3
|
+
import type { ChatStreamEvent, ChatUsage, ConversationMessage, ToolSummary } from "../types/daemon";
|
|
4
4
|
import { t } from "../i18n";
|
|
5
5
|
|
|
6
6
|
export type ToolStatus = "running" | "done" | "error" | "deduped";
|
|
@@ -37,6 +37,9 @@ export interface ChatMsg {
|
|
|
37
37
|
usage?: ChatUsage;
|
|
38
38
|
/** Operational notes (engine fallbacks, retries, suppressions). */
|
|
39
39
|
notes?: string[];
|
|
40
|
+
/** What a HISTORICAL turn did. Live turns render real ToolCall parts from
|
|
41
|
+
* the stream; replayed ones only have this, recorded at write time. */
|
|
42
|
+
toolSummary?: ToolSummary;
|
|
40
43
|
/** Skill Inspector decision for this turn (when the feature is on): which
|
|
41
44
|
* skills the per-turn RAG loaded inline vs merely hinted. */
|
|
42
45
|
inspector?: {
|
|
@@ -179,6 +182,7 @@ function threadToChatMsgs(messages: ConversationMessage[]): ChatMsg[] {
|
|
|
179
182
|
if (m.agent) turn.agentId = m.agent;
|
|
180
183
|
if (m.agent_name) turn.agent = m.agent_name;
|
|
181
184
|
if (m.model) turn.model = m.model;
|
|
185
|
+
if (m.tool_summary) turn.toolSummary = m.tool_summary;
|
|
182
186
|
if (m.usage) {
|
|
183
187
|
turn.usage = {
|
|
184
188
|
input_tokens: (turn.usage?.input_tokens || 0) + (m.usage.input_tokens || 0),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import useSWR from "swr";
|
|
2
|
+
import { NudgesApi } from "../lib/api/nudges";
|
|
3
|
+
import type { NudgeEntry, NudgePolicy, NudgeStats } from "../lib/api/nudges";
|
|
4
|
+
|
|
5
|
+
/** The ledger of unrequested messages, plus what they add up to. */
|
|
6
|
+
export function useNudges(limit = 50) {
|
|
7
|
+
const { data, error, isLoading, mutate } = useSWR<{
|
|
8
|
+
data: NudgeEntry[];
|
|
9
|
+
meta: { total: number; stats: NudgeStats };
|
|
10
|
+
}>(`/api/nudges?limit=${limit}`, () => NudgesApi.list(limit));
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
entries: data?.data ?? [],
|
|
14
|
+
stats: data?.meta?.stats,
|
|
15
|
+
error,
|
|
16
|
+
isLoading,
|
|
17
|
+
mutate,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The effective budget, and which layer set each part of it. */
|
|
22
|
+
export function useNudgePolicy() {
|
|
23
|
+
const { data, error, isLoading, mutate } = useSWR<{
|
|
24
|
+
policy: NudgePolicy;
|
|
25
|
+
source: string[];
|
|
26
|
+
defaults: NudgePolicy;
|
|
27
|
+
user_overrides: Partial<NudgePolicy>;
|
|
28
|
+
}>("/api/nudges/policy", () => NudgesApi.policy());
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
policy: data?.policy,
|
|
32
|
+
source: data?.source ?? [],
|
|
33
|
+
userOverrides: data?.user_overrides ?? {},
|
|
34
|
+
error,
|
|
35
|
+
isLoading,
|
|
36
|
+
mutate,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
// Keep entries in the same order as es.ts so diffs stay readable.
|
|
3
3
|
|
|
4
4
|
export const en = {
|
|
5
|
+
cron: {
|
|
6
|
+
daily: "every day at {at}",
|
|
7
|
+
weekdays: "Monday to Friday at {at}",
|
|
8
|
+
weekends: "weekends at {at}",
|
|
9
|
+
on_days: "{days} at {at}",
|
|
10
|
+
monthly: "day {day} of each month at {at}",
|
|
11
|
+
every_hour: "every hour, at :{minute}",
|
|
12
|
+
every_n_hours: "every {n} hours, at :{minute}",
|
|
13
|
+
every_n_minutes: "every {n} minutes",
|
|
14
|
+
every_minute: "every minute",
|
|
15
|
+
every_label: "every N hours instead",
|
|
16
|
+
use_cron: "edit as cron",
|
|
17
|
+
use_picker: "use the picker",
|
|
18
|
+
preset_every_day: "every day",
|
|
19
|
+
preset_weekdays: "weekdays",
|
|
20
|
+
preset_weekends: "weekends",
|
|
21
|
+
day_short: { sun: "Sun", mon: "Mon", tue: "Tue", wed: "Wed", thu: "Thu", fri: "Fri", sat: "Sat" },
|
|
22
|
+
},
|
|
23
|
+
when: {
|
|
24
|
+
now: "now",
|
|
25
|
+
in: "in {amount}",
|
|
26
|
+
ago: "{amount} ago",
|
|
27
|
+
minutes: "{n} min",
|
|
28
|
+
hours: "{n} h",
|
|
29
|
+
days: "{n} d",
|
|
30
|
+
},
|
|
5
31
|
common: {
|
|
6
32
|
loading: "Loadingโฆ",
|
|
7
33
|
saving: "Savingโฆ",
|
|
@@ -143,6 +169,11 @@ export const en = {
|
|
|
143
169
|
no_folders: "No folders.",
|
|
144
170
|
},
|
|
145
171
|
inbox: {
|
|
172
|
+
search: "Search",
|
|
173
|
+
no_match: "Nothing matches",
|
|
174
|
+
hide_quiet: "Hide quiet agents",
|
|
175
|
+
open_in_project: "Open in project",
|
|
176
|
+
super_agent_scope: "All projects",
|
|
146
177
|
title: "Agent inbox",
|
|
147
178
|
subtitle:
|
|
148
179
|
"Every agent as a conversation, most recent first.",
|
|
@@ -195,6 +226,8 @@ export const en = {
|
|
|
195
226
|
devices_revoke: "Revoke",
|
|
196
227
|
account_section: "Account",
|
|
197
228
|
agents_section: "Agents & models",
|
|
229
|
+
super_agent_section: "Super-agent",
|
|
230
|
+
knowledge_section: "Models & knowledge",
|
|
198
231
|
channels_section: "Channels & devices",
|
|
199
232
|
modules_section: "Modules",
|
|
200
233
|
advanced_section: "Advanced",
|
|
@@ -206,6 +239,7 @@ export const en = {
|
|
|
206
239
|
engines: "Engines & models",
|
|
207
240
|
telegram: "Telegram",
|
|
208
241
|
devices: "Devices",
|
|
242
|
+
nudge: "Interruptions",
|
|
209
243
|
advanced: "Advanced",
|
|
210
244
|
},
|
|
211
245
|
|
|
@@ -222,6 +256,9 @@ export const en = {
|
|
|
222
256
|
activate: "Activate",
|
|
223
257
|
replace_active: "Replace the active one",
|
|
224
258
|
deactivate: "Deactivate",
|
|
259
|
+
on: "Active",
|
|
260
|
+
off: "Inactive",
|
|
261
|
+
replaces_active: "Turning this on replaces the active profile",
|
|
225
262
|
deactivate_title: "Deactivate the profile?",
|
|
226
263
|
deactivate_confirm:
|
|
227
264
|
"APX goes back to vanilla. The profile's routines are disabled but not deleted, and your settings, tasks and memory are untouched โ activating it again restores everything.",
|
|
@@ -245,6 +282,34 @@ export const en = {
|
|
|
245
282
|
doctor_vanilla: "No profile active. APX behaves as it always has.",
|
|
246
283
|
},
|
|
247
284
|
|
|
285
|
+
nudge: {
|
|
286
|
+
title: "Interruption budget",
|
|
287
|
+
subtitle:
|
|
288
|
+
"How often APX may write to you without being asked. Replies to your own messages are never held back.",
|
|
289
|
+
off_hint:
|
|
290
|
+
"The budget is off: every unrequested message goes out. Everything is still recorded below.",
|
|
291
|
+
on_hint: "{{sent}} sent today, {{left}} left.",
|
|
292
|
+
source: "Set by",
|
|
293
|
+
enabled: "Enforce the budget",
|
|
294
|
+
daily_max: "Messages per day",
|
|
295
|
+
daily_max_hint: "0 means no ceiling.",
|
|
296
|
+
quiet_hours: "Quiet hours",
|
|
297
|
+
quiet_hours_hint: "HH:MM-HH:MM. Crosses midnight fine. Leave empty for none.",
|
|
298
|
+
cooldown: "Minimum gap (minutes)",
|
|
299
|
+
project_cooldown: "Minimum gap per project (minutes)",
|
|
300
|
+
kind_cooldown: "Minimum gap per kind (minutes)",
|
|
301
|
+
critical_bypass: "Critical messages may bypass the budget",
|
|
302
|
+
critical_bypass_hint: "Bypasses are always recorded and flagged.",
|
|
303
|
+
log_title: "What it has sent",
|
|
304
|
+
log_subtitle: "Unrequested messages only, newest first. Your rating feeds back into what it sends next.",
|
|
305
|
+
log_empty: "Nothing sent unprompted yet.",
|
|
306
|
+
useful: "Useful",
|
|
307
|
+
noise: "Not useful",
|
|
308
|
+
bypass: "bypassed the budget",
|
|
309
|
+
saved: "Budget updated",
|
|
310
|
+
unrated: "not rated",
|
|
311
|
+
},
|
|
312
|
+
|
|
248
313
|
identity: {
|
|
249
314
|
title: "Identity",
|
|
250
315
|
subtitle: "User data. Agent configuration goes in Super-agent.",
|
|
@@ -338,6 +403,7 @@ export const en = {
|
|
|
338
403
|
agents: "Agents",
|
|
339
404
|
routines: "Routines",
|
|
340
405
|
tasks: "Tasks",
|
|
406
|
+
commitments: "Commitments",
|
|
341
407
|
mcps: "MCPs",
|
|
342
408
|
artifacts: "Artifacts",
|
|
343
409
|
vars: "Variables",
|
|
@@ -442,6 +508,16 @@ export const en = {
|
|
|
442
508
|
},
|
|
443
509
|
|
|
444
510
|
global_tasks: {
|
|
511
|
+
|
|
512
|
+
add: "Add",
|
|
513
|
+
|
|
514
|
+
add_title: "New task",
|
|
515
|
+
|
|
516
|
+
field_title: "Task",
|
|
517
|
+
|
|
518
|
+
field_project: "Project",
|
|
519
|
+
|
|
520
|
+
field_due: "Due",
|
|
445
521
|
any_status: "any status",
|
|
446
522
|
title: "Tasks (all projects)",
|
|
447
523
|
subtitle: "Aggregated tasks from all registered projects.",
|
|
@@ -450,6 +526,44 @@ export const en = {
|
|
|
450
526
|
go_project: "Go to project",
|
|
451
527
|
},
|
|
452
528
|
|
|
529
|
+
commitments: {
|
|
530
|
+
|
|
531
|
+
add: "Add",
|
|
532
|
+
|
|
533
|
+
add_title: "Record a promise",
|
|
534
|
+
|
|
535
|
+
add_hint: "Something you told a specific person you would do. Tasks live next door.",
|
|
536
|
+
|
|
537
|
+
field_who: "Who is waiting",
|
|
538
|
+
|
|
539
|
+
field_who_hint: "A name as you would say it โ free text, not a contact.",
|
|
540
|
+
|
|
541
|
+
field_what: "What you promised",
|
|
542
|
+
|
|
543
|
+
field_what_ph: "send the revised quote",
|
|
544
|
+
|
|
545
|
+
field_due: "By when",
|
|
546
|
+
|
|
547
|
+
field_due_hint: "Optional, but it is the part that makes this useful.",
|
|
548
|
+
|
|
549
|
+
field_project: "Project",
|
|
550
|
+
title: "Commitments",
|
|
551
|
+
subtitle: "What was promised, to whom, and by when. Separate from tasks: someone is waiting on these.",
|
|
552
|
+
empty: "Nothing promised.",
|
|
553
|
+
overdue: "was due",
|
|
554
|
+
overdue_only: "Past their date",
|
|
555
|
+
no_date: "no date agreed",
|
|
556
|
+
moved: "moved",
|
|
557
|
+
mark_kept: "Kept",
|
|
558
|
+
mark_missed: "Missed",
|
|
559
|
+
state: {
|
|
560
|
+
open: "open",
|
|
561
|
+
kept: "kept",
|
|
562
|
+
missed: "missed",
|
|
563
|
+
all: "all",
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
|
|
453
567
|
routines: {
|
|
454
568
|
title: "Heartbeats / Routines",
|
|
455
569
|
subtitle: "Cron, every:Nm, once:ISO. Each routine fires an agent or a shell.",
|
|
@@ -784,6 +898,12 @@ export const en = {
|
|
|
784
898
|
},
|
|
785
899
|
|
|
786
900
|
memories: {
|
|
901
|
+
|
|
902
|
+
super_agent_group: "Super-agent",
|
|
903
|
+
|
|
904
|
+
notebook_item: "{persona}'s notebook",
|
|
905
|
+
|
|
906
|
+
tokens: "~{n} tok",
|
|
787
907
|
sidebar_title: "Memories",
|
|
788
908
|
general_group: "General",
|
|
789
909
|
general_item: "Project memory",
|
|
@@ -875,6 +995,8 @@ export const en = {
|
|
|
875
995
|
no_activity_ch: "No activity in channel \"{ch}\".",
|
|
876
996
|
error: "Could not read messages: {msg}",
|
|
877
997
|
show_more: "show more",
|
|
998
|
+
event_routine_created: "routine created",
|
|
999
|
+
event_routine_updated: "routine updated",
|
|
878
1000
|
show_less: "show less",
|
|
879
1001
|
daemon_errors: "Daemon errors (~/.apx/logs/errors.jsonl)",
|
|
880
1002
|
no_errors: "No errors recorded. ๐",
|
|
@@ -1566,6 +1688,9 @@ export const en = {
|
|
|
1566
1688
|
kind_routine: "routine",
|
|
1567
1689
|
kind_hierarchy: "hierarchy",
|
|
1568
1690
|
nodes_drag_hint: "{n} nodes ยท drag to rearrange",
|
|
1691
|
+
kind_watch: "Watcher",
|
|
1692
|
+
kind_watch_desc: "Sweeps for things worth noticing โ overdue work, promises coming due, projects gone quiet. Costs nothing when there is nothing to report.",
|
|
1693
|
+
action_watch: "Watches for signals, and judges only when it finds some",
|
|
1569
1694
|
kind_exec_agent: "Project agent",
|
|
1570
1695
|
kind_exec_agent_desc: "Runs a project agent with a prompt. You pick which one.",
|
|
1571
1696
|
kind_super_agent: "Super-agent",
|
|
@@ -1585,6 +1710,7 @@ export const en = {
|
|
|
1585
1710
|
preset_every_10m: "every 10 min",
|
|
1586
1711
|
preset_hourly: "hourly",
|
|
1587
1712
|
preset_daily_9am: "daily 9am",
|
|
1713
|
+
sched_manual: "only when you run it",
|
|
1588
1714
|
preset_weekdays_9am: "weekdays 9am",
|
|
1589
1715
|
preset_manual: "Manual",
|
|
1590
1716
|
var_pre_output_prompt: "Text output of the pre-commands. Replaced inside the prompt/text before it is sent. Use it to inject fresh data (weather, an API) into the instruction.",
|
|
@@ -1875,6 +2001,7 @@ export const en = {
|
|
|
1875
2001
|
shared_ui: {
|
|
1876
2002
|
skill_inspector_title: "Skill Inspector ({embedder}) chose these skills for this turn",
|
|
1877
2003
|
tools_count: "{n} tools",
|
|
2004
|
+
tools_failed: "{n} failed",
|
|
1878
2005
|
tool_read_file: "Read file",
|
|
1879
2006
|
tool_write_file: "Write file",
|
|
1880
2007
|
tool_edit_file: "Edit file",
|