@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.
Files changed (93) hide show
  1. package/package.json +1 -1
  2. package/src/core/agent/run-agent.js +30 -5
  3. package/src/core/agent/tool-summary.js +65 -0
  4. package/src/core/agent/tools/handlers/list-commitments.js +80 -0
  5. package/src/core/agent/tools/handlers/list-tasks.js +66 -27
  6. package/src/core/agent/tools/handlers/record-commitment.js +68 -0
  7. package/src/core/agent/tools/handlers/send-telegram.js +68 -2
  8. package/src/core/agent/tools/names.js +6 -0
  9. package/src/core/agent/tools/registry.js +9 -0
  10. package/src/core/agent/tools/tool-call-parser.js +70 -1
  11. package/src/core/channels/telegram/ask-callbacks.js +35 -0
  12. package/src/core/channels/telegram/dispatch.js +3 -0
  13. package/src/core/channels/telegram/reply.js +30 -5
  14. package/src/core/config/paths.js +3 -0
  15. package/src/core/config/redact.js +22 -0
  16. package/src/core/daemon/service.js +238 -0
  17. package/src/core/engines/gemini.js +322 -60
  18. package/src/core/engines/openai-compatible.js +21 -2
  19. package/src/core/memory/consolidate.js +225 -0
  20. package/src/core/nudge/index.js +192 -0
  21. package/src/core/nudge/policy.js +143 -0
  22. package/src/core/nudge/store.js +141 -0
  23. package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
  24. package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
  25. package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
  26. package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
  27. package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
  28. package/src/core/routines/runner.js +102 -3
  29. package/src/core/routines/signals.js +270 -0
  30. package/src/core/stores/commitments.js +331 -0
  31. package/src/core/stores/messages.js +4 -0
  32. package/src/core/stores/routines.js +17 -3
  33. package/src/core/util/thinking.js +51 -0
  34. package/src/host/daemon/api/commitments.js +135 -0
  35. package/src/host/daemon/api/nudges.js +112 -0
  36. package/src/host/daemon/api/routines.js +24 -0
  37. package/src/host/daemon/api/self-memory.js +50 -0
  38. package/src/host/daemon/api/telegram.js +42 -4
  39. package/src/host/daemon/api/voice.js +3 -1
  40. package/src/host/daemon/api.js +6 -0
  41. package/src/host/daemon/callback-reconciler.js +16 -0
  42. package/src/host/daemon/plugins/desktop/index.js +7 -1
  43. package/src/host/daemon/plugins/telegram/index.js +7 -2
  44. package/src/host/daemon/wakeup.js +17 -3
  45. package/src/interfaces/cli/commands/commitment.js +154 -0
  46. package/src/interfaces/cli/commands/daemon.js +57 -0
  47. package/src/interfaces/cli/commands/memory.js +73 -0
  48. package/src/interfaces/cli/commands/nudge.js +130 -0
  49. package/src/interfaces/cli/help/index.js +2 -2
  50. package/src/interfaces/cli/routes/commitment.js +19 -0
  51. package/src/interfaces/cli/routes/daemon.js +7 -1
  52. package/src/interfaces/cli/routes/index.js +4 -0
  53. package/src/interfaces/cli/routes/memory.js +10 -2
  54. package/src/interfaces/cli/routes/nudge.js +17 -0
  55. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +849 -0
  56. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
  57. package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
  58. package/src/interfaces/web/dist/index.html +2 -2
  59. package/src/interfaces/web/package-lock.json +11 -10
  60. package/src/interfaces/web/src/components/Section.tsx +18 -3
  61. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +13 -0
  62. package/src/interfaces/web/src/components/cron/CronPicker.tsx +196 -0
  63. package/src/interfaces/web/src/components/inbox/InboxList.tsx +145 -0
  64. package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +34 -4
  65. package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
  66. package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +12 -2
  67. package/src/interfaces/web/src/components/routines/shared.ts +14 -5
  68. package/src/interfaces/web/src/components/settings/NudgePanel.tsx +183 -0
  69. package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
  70. package/src/interfaces/web/src/components/ui/filter-chips.tsx +47 -0
  71. package/src/interfaces/web/src/components/ui.tsx +1 -0
  72. package/src/interfaces/web/src/constants/index.ts +1 -0
  73. package/src/interfaces/web/src/hooks/useChat.ts +5 -1
  74. package/src/interfaces/web/src/hooks/useNudges.ts +38 -0
  75. package/src/interfaces/web/src/i18n/en.ts +127 -0
  76. package/src/interfaces/web/src/i18n/es.ts +127 -0
  77. package/src/interfaces/web/src/lib/api/commitments.ts +57 -0
  78. package/src/interfaces/web/src/lib/api/notebook.ts +23 -0
  79. package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
  80. package/src/interfaces/web/src/lib/cron.ts +196 -0
  81. package/src/interfaces/web/src/lib/when.ts +32 -0
  82. package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
  83. package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
  84. package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
  85. package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
  86. package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +102 -19
  87. package/src/interfaces/web/src/screens/base/LogsTab.tsx +15 -0
  88. package/src/interfaces/web/src/screens/project/ChatTab.tsx +21 -3
  89. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +13 -11
  90. package/src/interfaces/web/src/types/daemon.ts +10 -1
  91. package/src/interfaces/web/dist/assets/index-CBR_-QyA.js +0 -824
  92. package/src/interfaces/web/dist/assets/index-CBR_-QyA.js.map +0 -1
  93. 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-2">
147
- {profile.active ? (
148
- <Button variant="destructive" loading={busy} onClick={() => setConfirmOff(true)}>
149
- {t("settings.profile.deactivate")}
150
- </Button>
151
- ) : (
152
- <Button variant="primary" loading={busy} onClick={() => activate(profile.id, !!active)}>
153
- {active ? t("settings.profile.replace_active") : t("settings.profile.activate")}
154
- </Button>
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
- {def.enum ? (
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";
@@ -65,6 +65,7 @@ export const ROUTINE_KINDS = [
65
65
  "super_agent",
66
66
  "telegram",
67
67
  "shell",
68
+ "watch",
68
69
  ] as const;
69
70
  export type RoutineKind = (typeof ROUTINE_KINDS)[number];
70
71
 
@@ -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",