mulmoclaude 0.6.2 → 0.6.4
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/README.md +26 -0
- package/bin/mulmoclaude.js +11 -1
- package/client/assets/JsonEditor-D6WBWLoa.js +10 -0
- package/client/assets/JsonEditor-Di5xGeZY.css +1 -0
- package/client/assets/_plugin-vue_export-helper-BOai-rQB.js +1 -0
- package/client/assets/chunk-D8eiyYIV-LcKZGJv5.js +1 -0
- package/client/assets/{html2canvas-CDGcmOD3-Bkf2uOth.js → html2canvas-CDGcmOD3-XVrO-eyz.js} +1 -1
- package/client/assets/index-CyBr8Mkr.css +2 -0
- package/client/assets/index-zZIqEbNX.js +5106 -0
- package/client/assets/{index.es-DqtpmBm8-D9mAh_KQ.js → index.es-DqtpmBm8-DHT6q10o.js} +1 -1
- package/client/assets/material-symbols-outlined-DtIK7AQn.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-D6kcV0wa.js +1 -0
- package/client/assets/{runtime-vue-BVUzgYGA.js → runtime-vue-fFYhnNg3.js} +1 -1
- package/client/assets/{vue-C8UuIO9J.js → vue-D4w8THF_.js} +1 -1
- package/client/assets/vue-i18n-CQbxVmNs.js +3 -0
- package/client/assets/vue.runtime.esm-bundler-BTyIdNAI.js +4 -0
- package/client/index.html +10 -10
- package/package.json +9 -8
- package/server/agent/backend/claude-code.ts +34 -0
- package/server/agent/backend/fake-echo.ts +370 -0
- package/server/agent/backend/index.ts +16 -1
- package/server/agent/config.ts +74 -24
- package/server/agent/index.ts +104 -80
- package/server/agent/mcpFailureMonitor.ts +167 -0
- package/server/agent/mcpPreflight.ts +185 -0
- package/server/agent/prompt.ts +50 -359
- package/server/agent/stdioHttpShim.ts +171 -0
- package/server/agent/stream.ts +12 -1
- package/server/api/routes/encore.ts +55 -0
- package/server/api/routes/files.ts +22 -0
- package/server/api/routes/mulmo-script.ts +19 -1
- package/server/api/routes/schedulerHandlers.ts +52 -4
- package/server/api/routes/sessions.ts +15 -0
- package/server/api/routes/skills.ts +263 -0
- package/server/build/dispatcher.mjs +299 -0
- package/server/encore/INVARIANTS.md +272 -0
- package/server/encore/boot.ts +39 -0
- package/server/encore/closure.ts +36 -0
- package/server/encore/cycle.ts +276 -0
- package/server/encore/dispatch.ts +103 -0
- package/server/encore/handlers/amend.ts +99 -0
- package/server/encore/handlers/appendNote.ts +74 -0
- package/server/encore/handlers/defineEncore.ts +42 -0
- package/server/encore/handlers/listTickets.ts +107 -0
- package/server/encore/handlers/markStepDone.ts +41 -0
- package/server/encore/handlers/markTargetSkipped.ts +33 -0
- package/server/encore/handlers/query.ts +138 -0
- package/server/encore/handlers/recordValues.ts +44 -0
- package/server/encore/handlers/resolveNotification.ts +121 -0
- package/server/encore/handlers/setup.ts +81 -0
- package/server/encore/handlers/shared.ts +137 -0
- package/server/encore/handlers/snooze.ts +87 -0
- package/server/encore/handlers/startObligationChat.ts +64 -0
- package/server/encore/handlers/startSetupChat.ts +50 -0
- package/server/encore/lock.ts +61 -0
- package/server/encore/notifier.ts +123 -0
- package/server/encore/obligation.ts +25 -0
- package/server/encore/paths.ts +78 -0
- package/server/encore/reconcile.ts +661 -0
- package/server/encore/tick.ts +191 -0
- package/server/encore/yaml-fm.ts +63 -0
- package/server/events/notifications.ts +19 -91
- package/server/index.ts +94 -9
- package/server/notifier/engine.ts +102 -1
- package/server/notifier/macosReminderAdapter.ts +30 -0
- package/server/notifier/runtime-api.ts +41 -1
- package/server/notifier/types.ts +15 -2
- package/server/plugins/runtime.ts +11 -2
- package/server/prompts/index.ts +39 -0
- package/server/prompts/system/journal-pointer.md +12 -0
- package/server/prompts/system/memory-management-atomic.md +33 -0
- package/server/prompts/system/memory-management-topic.md +60 -0
- package/server/prompts/system/news-concierge.md +24 -0
- package/server/prompts/system/sandbox-tools.md +10 -0
- package/server/prompts/system/sources-context.md +16 -0
- package/server/prompts/system/system.md +91 -0
- package/server/system/announceOptionalDeps.ts +57 -0
- package/server/system/appVersion.ts +34 -0
- package/server/system/config.ts +17 -1
- package/server/system/docker.ts +14 -6
- package/server/system/env.ts +18 -5
- package/server/system/optionalDeps.ts +129 -0
- package/server/utils/cli-flags.d.mts +14 -0
- package/server/utils/cli-flags.mjs +53 -0
- package/server/utils/files/encore-io.ts +111 -0
- package/server/utils/time.ts +6 -0
- package/server/workspace/helps/business.md +2 -2
- package/server/workspace/helps/encore-dsl.md +482 -0
- package/server/workspace/helps/index.md +15 -13
- package/server/workspace/helps/mulmoscript.md +3 -3
- package/server/workspace/helps/sandbox.md +2 -2
- package/server/workspace/hooks/dispatcher.ts +7 -5
- package/server/workspace/hooks/provision.ts +6 -3
- package/server/workspace/paths.ts +13 -4
- package/server/workspace/skills/catalog.ts +355 -0
- package/server/workspace/skills/external/catalog.ts +283 -0
- package/server/workspace/skills/external/clone.ts +129 -0
- package/server/workspace/skills/external/id.ts +194 -0
- package/server/workspace/skills/external/install.ts +417 -0
- package/server/workspace/skills/external/presets.ts +50 -0
- package/server/workspace/skills-preset.ts +29 -17
- package/server/workspace/workspace.ts +10 -5
- package/src/App.vue +37 -8
- package/src/components/FileContentRenderer.vue +102 -9
- package/src/components/JsonEditor.vue +160 -0
- package/src/components/NotificationBell.vue +35 -3
- package/src/components/PluginLauncher.vue +20 -41
- package/src/components/RightSidebar.vue +19 -0
- package/src/components/SettingsMcpTab.vue +58 -11
- package/src/components/SettingsModal.vue +22 -1
- package/src/components/StackView.vue +10 -1
- package/src/components/TodoExplorer.vue +16 -0
- package/src/components/todo/TodoKanbanView.vue +34 -6
- package/src/composables/useNotifications.ts +21 -1
- package/src/config/apiRoutes.ts +0 -6
- package/src/config/mcpCatalog.ts +12 -7
- package/src/config/mcpTypes.ts +5 -0
- package/src/config/roles.ts +52 -15
- package/src/config/systemFileDescriptors.ts +12 -0
- package/src/lang/de.ts +108 -12
- package/src/lang/en.ts +105 -11
- package/src/lang/es.ts +106 -11
- package/src/lang/fr.ts +106 -11
- package/src/lang/ja.ts +104 -11
- package/src/lang/ko.ts +105 -11
- package/src/lang/pt-BR.ts +106 -11
- package/src/lang/zh.ts +103 -11
- package/src/main.ts +1 -0
- package/src/plugins/_generated/metas.ts +4 -0
- package/src/plugins/_generated/registrations.ts +2 -0
- package/src/plugins/_generated/server-bindings.ts +5 -0
- package/src/plugins/encore/EncoreDashboard.vue +504 -0
- package/src/plugins/encore/EncoreRedirect.vue +116 -0
- package/src/plugins/encore/View.vue +36 -0
- package/src/plugins/encore/defineEncoreDefinition.ts +74 -0
- package/src/plugins/encore/defineEncoreMeta.ts +13 -0
- package/src/plugins/encore/index.ts +93 -0
- package/src/plugins/encore/manageEncoreDefinition.ts +100 -0
- package/src/plugins/encore/manageEncoreMeta.ts +36 -0
- package/src/plugins/manageSkills/View.vue +832 -30
- package/src/plugins/manageSkills/categories.ts +125 -0
- package/src/plugins/manageSkills/meta.ts +30 -0
- package/src/plugins/markdown/definition.ts +3 -3
- package/src/plugins/meta-types.ts +5 -0
- package/src/plugins/presentMulmoScript/Preview.vue +3 -3
- package/src/plugins/presentMulmoScript/View.vue +157 -33
- package/src/plugins/presentMulmoScript/meta.ts +4 -0
- package/src/plugins/scheduler/View.vue +45 -9
- package/src/plugins/scheduler/calendarDefinition.ts +6 -2
- package/src/plugins/scheduler/multiDayHelpers.ts +95 -0
- package/src/plugins/skill/View.vue +1 -5
- package/src/plugins/spreadsheet/View.vue +3 -3
- package/src/plugins/spreadsheet/definition.ts +1 -1
- package/src/plugins/textResponse/Preview.vue +14 -1
- package/src/plugins/textResponse/View.vue +39 -24
- package/src/plugins/wiki/components/WikiPageBody.vue +4 -0
- package/src/router/index.ts +11 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/types/encore-dsl/at-expression.ts +120 -0
- package/src/types/encore-dsl/at-resolver.ts +32 -0
- package/src/types/encore-dsl/cadence.ts +289 -0
- package/src/types/encore-dsl/schema.ts +288 -0
- package/src/types/notification.ts +2 -1
- package/src/types/session.ts +6 -0
- package/src/types/sse.ts +5 -0
- package/src/types/toolCallHistory.ts +7 -0
- package/src/utils/agent/eventDispatch.ts +26 -5
- package/src/utils/agent/mcpHint.ts +50 -0
- package/src/utils/image/htmlSrcAttrs.ts +117 -13
- package/src/utils/session/sessionEntries.ts +8 -32
- package/client/assets/PluginScopedRoot-YjvQq0Nn.js +0 -3
- package/client/assets/chunk-CernVdwh.js +0 -1
- package/client/assets/chunk-D8eiyYIV-CAXpUwLd.js +0 -1
- package/client/assets/index-BwrlMMHr.js +0 -5005
- package/client/assets/index-CvvNuegU.css +0 -2
- package/client/assets/material-symbols-outlined-BOZVWuR3.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-C1To4M3t.js +0 -1
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +0 -4
- package/server/api/routes/notifications.ts +0 -195
- package/server/notifier/legacy-adapters.ts +0 -76
- package/server/workspace/hooks/dispatcher.mjs +0 -300
- package/src/composables/useSelectedResult.ts +0 -49
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Encore cadence math. Pure functions — no fs, no clock injection
|
|
2
|
+
// here (the caller passes `now` explicitly). Per the DSL spec each
|
|
3
|
+
// cadence shape determines:
|
|
4
|
+
//
|
|
5
|
+
// - which slot "now" falls into (currentCycleSlot)
|
|
6
|
+
// - the cycle's start date (cycleStart)
|
|
7
|
+
// - the cycle's deadline date (cycleDeadline)
|
|
8
|
+
// - the on-disk cycle id string (formatCycleId), used to name the
|
|
9
|
+
// per-cycle file under obligations/<id>/<cycleId>.md
|
|
10
|
+
//
|
|
11
|
+
// The five v1 cadences are: annual, biannual, monthly, weekly, daily.
|
|
12
|
+
// Quarterly / one-shot / custom-cron are out of scope. Days are
|
|
13
|
+
// capped at 28 in validators to avoid February edge cases; the math
|
|
14
|
+
// here trusts validated input. ISO dates everywhere (YYYY-MM-DD).
|
|
15
|
+
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
// ── ISO date helpers ────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/** Format a Date as `YYYY-MM-DD` in the server's LOCAL timezone.
|
|
21
|
+
* Encore obligations are wall-clock obligations ("pay rent on the
|
|
22
|
+
* 1st" means the 1st in the user's calendar, not the 1st in UTC),
|
|
23
|
+
* so cycle boundaries must follow the host's wall clock. Matches
|
|
24
|
+
* the journal subsystem's `toLocalIsoDate` convention in
|
|
25
|
+
* `server/utils/date.ts`. */
|
|
26
|
+
export function isoDate(date: Date): string {
|
|
27
|
+
const year = date.getFullYear();
|
|
28
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
29
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
30
|
+
return `${year}-${month}-${day}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function localDate(year: number, monthZero: number, day: number): Date {
|
|
34
|
+
return new Date(year, monthZero, day);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Parse a YYYY-MM-DD string to a local-midnight Date. */
|
|
38
|
+
function parseLocalIsoDate(iso: string): Date {
|
|
39
|
+
const [year, month, day] = iso.split("-").map(Number);
|
|
40
|
+
return new Date(year, month - 1, day);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Add N calendar days to an ISO date (returns ISO). */
|
|
44
|
+
export function addDays(iso: string, days: number): string {
|
|
45
|
+
const [year, month, day] = iso.split("-").map(Number);
|
|
46
|
+
return isoDate(localDate(year, month - 1, day + days));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Lexicographic ISO compare. -1 / 0 / 1. */
|
|
50
|
+
export function compareIsoDates(lhs: string, rhs: string): number {
|
|
51
|
+
if (lhs < rhs) return -1;
|
|
52
|
+
if (lhs > rhs) return 1;
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── ISO week helpers (used by weekly cadence) ───────────────────
|
|
57
|
+
|
|
58
|
+
const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
59
|
+
|
|
60
|
+
/** ISO 8601 week number for a date. The ISO week year matches the
|
|
61
|
+
* Thursday of the week — handy for the early-Jan / late-Dec edge
|
|
62
|
+
* cases where a date's calendar year and ISO-week year differ. */
|
|
63
|
+
function isoWeekYearAndNumber(date: Date): { year: number; week: number } {
|
|
64
|
+
// Copy to avoid mutating caller.
|
|
65
|
+
const thursdayOfWeek = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
66
|
+
const dayNum = (thursdayOfWeek.getDay() + 6) % 7; // Mon=0 … Sun=6
|
|
67
|
+
thursdayOfWeek.setDate(thursdayOfWeek.getDate() - dayNum + 3);
|
|
68
|
+
const firstThursday = new Date(thursdayOfWeek.getFullYear(), 0, 4);
|
|
69
|
+
const firstThursdayDayNum = (firstThursday.getDay() + 6) % 7;
|
|
70
|
+
const firstThursdayWeekStart = new Date(firstThursday);
|
|
71
|
+
firstThursdayWeekStart.setDate(firstThursday.getDate() - firstThursdayDayNum);
|
|
72
|
+
const diffMs = thursdayOfWeek.getTime() - firstThursdayWeekStart.getTime();
|
|
73
|
+
const week = 1 + Math.round(diffMs / MS_PER_WEEK);
|
|
74
|
+
return { year: thursdayOfWeek.getFullYear(), week };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Monday of the given ISO week (returns ISO date). */
|
|
78
|
+
function isoWeekMonday(year: number, week: number): string {
|
|
79
|
+
// Jan 4 is always in week 1.
|
|
80
|
+
const jan4 = new Date(year, 0, 4);
|
|
81
|
+
const jan4DayNum = (jan4.getDay() + 6) % 7;
|
|
82
|
+
const mondayOfWeek1 = new Date(jan4);
|
|
83
|
+
mondayOfWeek1.setDate(jan4.getDate() - jan4DayNum);
|
|
84
|
+
const target = new Date(mondayOfWeek1);
|
|
85
|
+
target.setDate(mondayOfWeek1.getDate() + (week - 1) * 7);
|
|
86
|
+
return isoDate(target);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Cadence Zod schema ──────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
const dayInMonth = z.number().int().min(1).max(28);
|
|
92
|
+
const monthOfYear = z.number().int().min(1).max(12);
|
|
93
|
+
|
|
94
|
+
const cycleEntry = z.object({ month: monthOfYear, day: dayInMonth });
|
|
95
|
+
|
|
96
|
+
const annual = z.object({
|
|
97
|
+
type: z.literal("annual"),
|
|
98
|
+
cycles: z.tuple([cycleEntry]),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const biannual = z.object({
|
|
102
|
+
type: z.literal("biannual"),
|
|
103
|
+
cycles: z.tuple([cycleEntry, cycleEntry]),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const monthly = z.object({
|
|
107
|
+
type: z.literal("monthly"),
|
|
108
|
+
day: dayInMonth,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const DAY_OF_WEEK = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] as const;
|
|
112
|
+
export type DayOfWeek = (typeof DAY_OF_WEEK)[number];
|
|
113
|
+
|
|
114
|
+
const weekly = z.object({
|
|
115
|
+
type: z.literal("weekly"),
|
|
116
|
+
dayOfWeek: z.enum(DAY_OF_WEEK),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const daily = z.object({
|
|
120
|
+
type: z.literal("daily"),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export const CadenceSchema = z.discriminatedUnion("type", [annual, biannual, monthly, weekly, daily]).superRefine((cadence, ctx) => {
|
|
124
|
+
if (cadence.type === "biannual") {
|
|
125
|
+
const [first, second] = cadence.cycles;
|
|
126
|
+
if (first.month > second.month || (first.month === second.month && first.day >= second.day)) {
|
|
127
|
+
ctx.addIssue({
|
|
128
|
+
code: z.ZodIssueCode.custom,
|
|
129
|
+
message: "biannual cycles must be in calendar order (first cycle before second)",
|
|
130
|
+
path: ["cycles"],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export type Cadence = z.infer<typeof CadenceSchema>;
|
|
137
|
+
|
|
138
|
+
// ── Slot identification ─────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
export type CycleSlot =
|
|
141
|
+
| { kind: "annual"; year: number }
|
|
142
|
+
| { kind: "biannual"; year: number; half: 1 | 2 }
|
|
143
|
+
| { kind: "monthly"; year: number; month: number }
|
|
144
|
+
| { kind: "weekly"; year: number; week: number }
|
|
145
|
+
| { kind: "daily"; iso: string };
|
|
146
|
+
|
|
147
|
+
/** Find the cycle slot a given date falls into for this cadence.
|
|
148
|
+
* "Falls into" = the slot whose [start, deadline] range contains
|
|
149
|
+
* `now`; for annual / biannual we pick the slot whose deadline is
|
|
150
|
+
* the next upcoming one (so the cycle is "active" until its
|
|
151
|
+
* deadline passes; the day AFTER the deadline rolls into the next
|
|
152
|
+
* cycle, matching the spec's cycle-start = day-after-prev-deadline
|
|
153
|
+
* rule). */
|
|
154
|
+
export function currentCycleSlot(cadence: Cadence, now: Date): CycleSlot {
|
|
155
|
+
// Compare as ISO date strings — the deadline is date-only, and
|
|
156
|
+
// comparing `now` (a full timestamp) against a midnight Date
|
|
157
|
+
// would roll over one day early on the deadline day. ISO-string
|
|
158
|
+
// lexical compare is calendar-correct for YYYY-MM-DD.
|
|
159
|
+
const todayIso = isoDate(now);
|
|
160
|
+
const year = now.getFullYear();
|
|
161
|
+
if (cadence.type === "annual") {
|
|
162
|
+
const [{ month, day }] = cadence.cycles;
|
|
163
|
+
const thisYearDeadlineIso = isoDate(localDate(year, month - 1, day));
|
|
164
|
+
return { kind: "annual", year: todayIso <= thisYearDeadlineIso ? year : year + 1 };
|
|
165
|
+
}
|
|
166
|
+
if (cadence.type === "biannual") {
|
|
167
|
+
const [first, second] = cadence.cycles;
|
|
168
|
+
const firstDeadlineIso = isoDate(localDate(year, first.month - 1, first.day));
|
|
169
|
+
const secondDeadlineIso = isoDate(localDate(year, second.month - 1, second.day));
|
|
170
|
+
if (todayIso <= firstDeadlineIso) return { kind: "biannual", year, half: 1 };
|
|
171
|
+
if (todayIso <= secondDeadlineIso) return { kind: "biannual", year, half: 2 };
|
|
172
|
+
return { kind: "biannual", year: year + 1, half: 1 };
|
|
173
|
+
}
|
|
174
|
+
if (cadence.type === "monthly") {
|
|
175
|
+
const monthZero = now.getMonth();
|
|
176
|
+
const deadlineThisMonthIso = isoDate(localDate(year, monthZero, cadence.day));
|
|
177
|
+
if (todayIso <= deadlineThisMonthIso) {
|
|
178
|
+
return { kind: "monthly", year, month: monthZero + 1 };
|
|
179
|
+
}
|
|
180
|
+
const next = localDate(year, monthZero + 1, 1);
|
|
181
|
+
return { kind: "monthly", year: next.getFullYear(), month: next.getMonth() + 1 };
|
|
182
|
+
}
|
|
183
|
+
if (cadence.type === "weekly") {
|
|
184
|
+
const targetIdx = DAY_OF_WEEK.indexOf(cadence.dayOfWeek);
|
|
185
|
+
const { year: isoYear, week } = isoWeekYearAndNumber(now);
|
|
186
|
+
const monday = isoWeekMonday(isoYear, week);
|
|
187
|
+
const deadlineThisWeek = addDays(monday, targetIdx);
|
|
188
|
+
if (isoDate(now) <= deadlineThisWeek) {
|
|
189
|
+
return { kind: "weekly", year: isoYear, week };
|
|
190
|
+
}
|
|
191
|
+
const nextMondayIso = addDays(monday, 7);
|
|
192
|
+
const next = isoWeekYearAndNumber(parseLocalIsoDate(nextMondayIso));
|
|
193
|
+
return { kind: "weekly", year: next.year, week: next.week };
|
|
194
|
+
}
|
|
195
|
+
// daily
|
|
196
|
+
return { kind: "daily", iso: isoDate(now) };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** ISO date of the cycle's deadline. */
|
|
200
|
+
export function cycleDeadline(cadence: Cadence, slot: CycleSlot): string {
|
|
201
|
+
if (cadence.type === "annual" && slot.kind === "annual") {
|
|
202
|
+
const [{ month, day }] = cadence.cycles;
|
|
203
|
+
return isoDate(localDate(slot.year, month - 1, day));
|
|
204
|
+
}
|
|
205
|
+
if (cadence.type === "biannual" && slot.kind === "biannual") {
|
|
206
|
+
const entry = cadence.cycles[slot.half - 1];
|
|
207
|
+
return isoDate(localDate(slot.year, entry.month - 1, entry.day));
|
|
208
|
+
}
|
|
209
|
+
if (cadence.type === "monthly" && slot.kind === "monthly") {
|
|
210
|
+
return isoDate(localDate(slot.year, slot.month - 1, cadence.day));
|
|
211
|
+
}
|
|
212
|
+
if (cadence.type === "weekly" && slot.kind === "weekly") {
|
|
213
|
+
const monday = isoWeekMonday(slot.year, slot.week);
|
|
214
|
+
const targetIdx = DAY_OF_WEEK.indexOf(cadence.dayOfWeek);
|
|
215
|
+
return addDays(monday, targetIdx);
|
|
216
|
+
}
|
|
217
|
+
if (cadence.type === "daily" && slot.kind === "daily") {
|
|
218
|
+
return slot.iso;
|
|
219
|
+
}
|
|
220
|
+
throw new Error(`cadence/slot mismatch: cadence.type=${cadence.type} slot.kind=${slot.kind}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** ISO date of the cycle's start. For annual/biannual that's the
|
|
224
|
+
* day after the previous slot's deadline; for monthly the 1st of
|
|
225
|
+
* the month; weekly the Monday of the ISO week; daily same as
|
|
226
|
+
* deadline. */
|
|
227
|
+
export function cycleStart(cadence: Cadence, slot: CycleSlot): string {
|
|
228
|
+
if (cadence.type === "annual" && slot.kind === "annual") {
|
|
229
|
+
const [{ month, day }] = cadence.cycles;
|
|
230
|
+
const prevDeadline = isoDate(localDate(slot.year - 1, month - 1, day));
|
|
231
|
+
return addDays(prevDeadline, 1);
|
|
232
|
+
}
|
|
233
|
+
if (cadence.type === "biannual" && slot.kind === "biannual") {
|
|
234
|
+
if (slot.half === 1) {
|
|
235
|
+
const [, second] = cadence.cycles;
|
|
236
|
+
const prevDeadline = isoDate(localDate(slot.year - 1, second.month - 1, second.day));
|
|
237
|
+
return addDays(prevDeadline, 1);
|
|
238
|
+
}
|
|
239
|
+
const [first] = cadence.cycles;
|
|
240
|
+
const prevDeadline = isoDate(localDate(slot.year, first.month - 1, first.day));
|
|
241
|
+
return addDays(prevDeadline, 1);
|
|
242
|
+
}
|
|
243
|
+
if (cadence.type === "monthly" && slot.kind === "monthly") {
|
|
244
|
+
return isoDate(localDate(slot.year, slot.month - 1, 1));
|
|
245
|
+
}
|
|
246
|
+
if (cadence.type === "weekly" && slot.kind === "weekly") {
|
|
247
|
+
return isoWeekMonday(slot.year, slot.week);
|
|
248
|
+
}
|
|
249
|
+
if (cadence.type === "daily" && slot.kind === "daily") {
|
|
250
|
+
return slot.iso;
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`cadence/slot mismatch: cadence.type=${cadence.type} slot.kind=${slot.kind}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** On-disk cycle id string. Stable / deterministic — used as the
|
|
256
|
+
* per-cycle markdown file name under obligations/<id>/<cycleId>.md. */
|
|
257
|
+
export function formatCycleId(slot: CycleSlot): string {
|
|
258
|
+
if (slot.kind === "annual") return `${slot.year}`;
|
|
259
|
+
if (slot.kind === "biannual") return `${slot.year}-h${slot.half}`;
|
|
260
|
+
if (slot.kind === "monthly") return `${slot.year}-${String(slot.month).padStart(2, "0")}`;
|
|
261
|
+
if (slot.kind === "weekly") return `${slot.year}-W${String(slot.week).padStart(2, "0")}`;
|
|
262
|
+
return slot.iso;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Advance to the next slot after `current` for this cadence. Used
|
|
266
|
+
* by next-cycle provisioning when a cycle closes. */
|
|
267
|
+
export function nextSlot(cadence: Cadence, current: CycleSlot): CycleSlot {
|
|
268
|
+
if (cadence.type === "annual" && current.kind === "annual") {
|
|
269
|
+
return { kind: "annual", year: current.year + 1 };
|
|
270
|
+
}
|
|
271
|
+
if (cadence.type === "biannual" && current.kind === "biannual") {
|
|
272
|
+
if (current.half === 1) return { kind: "biannual", year: current.year, half: 2 };
|
|
273
|
+
return { kind: "biannual", year: current.year + 1, half: 1 };
|
|
274
|
+
}
|
|
275
|
+
if (cadence.type === "monthly" && current.kind === "monthly") {
|
|
276
|
+
const next = localDate(current.year, current.month, 1);
|
|
277
|
+
return { kind: "monthly", year: next.getFullYear(), month: next.getMonth() + 1 };
|
|
278
|
+
}
|
|
279
|
+
if (cadence.type === "weekly" && current.kind === "weekly") {
|
|
280
|
+
const monday = isoWeekMonday(current.year, current.week);
|
|
281
|
+
const nextMondayIso = addDays(monday, 7);
|
|
282
|
+
const { year, week } = isoWeekYearAndNumber(parseLocalIsoDate(nextMondayIso));
|
|
283
|
+
return { kind: "weekly", year, week };
|
|
284
|
+
}
|
|
285
|
+
if (cadence.type === "daily" && current.kind === "daily") {
|
|
286
|
+
return { kind: "daily", iso: addDays(current.iso, 1) };
|
|
287
|
+
}
|
|
288
|
+
throw new Error(`cadence/slot mismatch: cadence.type=${cadence.type} current.kind=${current.kind}`);
|
|
289
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Encore DSL Zod schema.
|
|
2
|
+
//
|
|
3
|
+
// One obligation = one DSL document, validated end-to-end on every
|
|
4
|
+
// setup / amendDefinition call. The discriminated union on `type`
|
|
5
|
+
// (payment | service) enforces `currency` required iff payment. The
|
|
6
|
+
// cross-field rules (cadence cycle-count, step-field ownership, at-
|
|
7
|
+
// expression validity, etc.) live in superRefine blocks at the leaf
|
|
8
|
+
// or document level.
|
|
9
|
+
//
|
|
10
|
+
// Naming convention (a deliberate distinction):
|
|
11
|
+
// - KEBAB regex (slug ids): obligation id, target id, step id —
|
|
12
|
+
// these become file/folder names and routes, so case-sensitive
|
|
13
|
+
// paths matter.
|
|
14
|
+
// - IDENTIFIER regex (field names): camelCase or kebab — Claude
|
|
15
|
+
// composes "invoiceReceivedOn" / "paidOn" naturally and the
|
|
16
|
+
// surrounding codebase uses camelCase identifiers everywhere.
|
|
17
|
+
//
|
|
18
|
+
// See plans/feat-encore-plugin.md §The Encore DSL for the full
|
|
19
|
+
// natural-language spec; this file is its executable form.
|
|
20
|
+
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { CadenceSchema } from "./cadence.js";
|
|
23
|
+
import { atExprSchema, parseAtExpression } from "./at-expression.js";
|
|
24
|
+
import { resolveAtExpression } from "./at-resolver.js";
|
|
25
|
+
|
|
26
|
+
const KEBAB = /^[a-z][a-z0-9-]*$/;
|
|
27
|
+
const IDENTIFIER = /^[a-z][a-zA-Z0-9_-]*$/;
|
|
28
|
+
const ISO_4217 = /^[A-Z]{3}$/;
|
|
29
|
+
|
|
30
|
+
const kebabId = z.string().regex(KEBAB, "must be kebab-case (lowercase letters, digits, hyphens; starts with a letter)");
|
|
31
|
+
const fieldName = z.string().regex(IDENTIFIER, "must be a valid identifier (lowercase start; letters / digits / _ / -)");
|
|
32
|
+
|
|
33
|
+
// ── formSchema field ────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const FIELD_TYPES = ["string", "text", "url", "email", "date", "number", "boolean", "enum"] as const;
|
|
36
|
+
export type FormFieldType = (typeof FIELD_TYPES)[number];
|
|
37
|
+
|
|
38
|
+
const FormField = z
|
|
39
|
+
.object({
|
|
40
|
+
name: fieldName,
|
|
41
|
+
type: z.enum(FIELD_TYPES),
|
|
42
|
+
label: z.string().min(1),
|
|
43
|
+
required: z.boolean().optional(),
|
|
44
|
+
placeholder: z.string().optional(),
|
|
45
|
+
options: z.array(z.string().min(1)).optional(),
|
|
46
|
+
})
|
|
47
|
+
.superRefine((field, ctx) => {
|
|
48
|
+
if (field.type === "enum") {
|
|
49
|
+
if (!field.options || field.options.length === 0) {
|
|
50
|
+
ctx.addIssue({
|
|
51
|
+
code: z.ZodIssueCode.custom,
|
|
52
|
+
message: "type=enum requires non-empty options[]",
|
|
53
|
+
path: ["options"],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
} else if (field.options && field.options.length > 0) {
|
|
57
|
+
ctx.addIssue({
|
|
58
|
+
code: z.ZodIssueCode.custom,
|
|
59
|
+
message: `options[] is only meaningful for type=enum; got type=${field.type}`,
|
|
60
|
+
path: ["options"],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export type FormFieldDef = z.infer<typeof FormField>;
|
|
66
|
+
|
|
67
|
+
const FormSchema = z.object({
|
|
68
|
+
fields: z.array(FormField).min(1),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ── target / step ───────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const Target = z.object({
|
|
74
|
+
id: kebabId,
|
|
75
|
+
displayName: z.string().min(1),
|
|
76
|
+
/** Pre-fill map — field-name → default value. The LLM treats
|
|
77
|
+
* presence of a default as "don't ask; auto-record". Validated
|
|
78
|
+
* against formSchema in the doc-level superRefine. */
|
|
79
|
+
defaults: z.record(z.string(), z.unknown()).optional(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export type TargetDef = z.infer<typeof Target>;
|
|
83
|
+
|
|
84
|
+
const Severity = z.enum(["info", "warning", "urgent"]);
|
|
85
|
+
export type Severity = z.infer<typeof Severity>;
|
|
86
|
+
|
|
87
|
+
const FiringPhase = z.object({
|
|
88
|
+
at: atExprSchema({ allowStepDeadline: true }),
|
|
89
|
+
severity: Severity,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const Step = z.object({
|
|
93
|
+
id: kebabId,
|
|
94
|
+
displayName: z.string().min(1),
|
|
95
|
+
/** When this step's deadline falls. Resolved against
|
|
96
|
+
* cycleStart / cycleDeadline — step-deadline anchor not allowed
|
|
97
|
+
* here (it would be self-referential). */
|
|
98
|
+
deadline: atExprSchema({ allowStepDeadline: false }),
|
|
99
|
+
firingPlan: z.array(FiringPhase).min(1),
|
|
100
|
+
/** Field names this step is responsible for. Must reference
|
|
101
|
+
* existing formSchema field names; checked at document level. */
|
|
102
|
+
fields: z.array(fieldName),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export type StepDef = z.infer<typeof Step>;
|
|
106
|
+
|
|
107
|
+
// ── carryForward ────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
const CarryForward = z.object({
|
|
110
|
+
body: z.enum(["empty", "copy"]).default("empty"),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── top-level DSL (discriminated union on type) ─────────────────
|
|
114
|
+
|
|
115
|
+
const STATUS = ["active", "paused", "retired"] as const;
|
|
116
|
+
|
|
117
|
+
const sharedFields = {
|
|
118
|
+
version: z.literal(1),
|
|
119
|
+
/** Generated server-side from displayName at setup; the DSL Claude
|
|
120
|
+
* composes omits it. We keep the field optional in the input
|
|
121
|
+
* schema so amend operations work; setup explicitly strips. */
|
|
122
|
+
id: kebabId.optional(),
|
|
123
|
+
displayName: z.string().min(1),
|
|
124
|
+
status: z.enum(STATUS).default("active"),
|
|
125
|
+
/** Generated server-side at setup. */
|
|
126
|
+
createdAt: z.string().optional(),
|
|
127
|
+
|
|
128
|
+
cadence: CadenceSchema,
|
|
129
|
+
targets: z.array(Target).min(1),
|
|
130
|
+
steps: z.array(Step).min(1),
|
|
131
|
+
formSchema: FormSchema,
|
|
132
|
+
carryForward: CarryForward.optional(),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const PaymentDsl = z.object({
|
|
136
|
+
...sharedFields,
|
|
137
|
+
type: z.literal("payment"),
|
|
138
|
+
currency: z.string().regex(ISO_4217, "currency must be a 3-letter uppercase ISO 4217 code (e.g. JPY, USD, EUR)"),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const ServiceDsl = z.object({
|
|
142
|
+
...sharedFields,
|
|
143
|
+
type: z.literal("service"),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ── cross-field validators (split out so the per-document
|
|
147
|
+
// superRefine stays under the cognitive-complexity threshold) ─────
|
|
148
|
+
|
|
149
|
+
interface Doc {
|
|
150
|
+
targets: TargetDef[];
|
|
151
|
+
steps: StepDef[];
|
|
152
|
+
formSchema: { fields: FormFieldDef[] };
|
|
153
|
+
}
|
|
154
|
+
type Ctx = z.RefinementCtx;
|
|
155
|
+
|
|
156
|
+
function validateUniqueIds(doc: Doc, ctx: Ctx): { fieldNames: Set<string> } {
|
|
157
|
+
const targetIds = new Set<string>();
|
|
158
|
+
for (const target of doc.targets) {
|
|
159
|
+
if (targetIds.has(target.id)) {
|
|
160
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate target id ${JSON.stringify(target.id)}`, path: ["targets"] });
|
|
161
|
+
}
|
|
162
|
+
targetIds.add(target.id);
|
|
163
|
+
}
|
|
164
|
+
const stepIds = new Set<string>();
|
|
165
|
+
for (const step of doc.steps) {
|
|
166
|
+
if (stepIds.has(step.id)) {
|
|
167
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate step id ${JSON.stringify(step.id)}`, path: ["steps"] });
|
|
168
|
+
}
|
|
169
|
+
stepIds.add(step.id);
|
|
170
|
+
}
|
|
171
|
+
const fieldNames = new Set<string>();
|
|
172
|
+
for (const field of doc.formSchema.fields) {
|
|
173
|
+
if (fieldNames.has(field.name)) {
|
|
174
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate formSchema field name ${JSON.stringify(field.name)}`, path: ["formSchema", "fields"] });
|
|
175
|
+
}
|
|
176
|
+
fieldNames.add(field.name);
|
|
177
|
+
}
|
|
178
|
+
return { fieldNames };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function validateFieldOwnership(doc: Doc, fieldNames: Set<string>, ctx: Ctx): void {
|
|
182
|
+
const claims = new Map<string, string[]>();
|
|
183
|
+
for (const step of doc.steps) {
|
|
184
|
+
for (const fname of step.fields) {
|
|
185
|
+
if (!fieldNames.has(fname)) {
|
|
186
|
+
ctx.addIssue({
|
|
187
|
+
code: z.ZodIssueCode.custom,
|
|
188
|
+
message: `step ${JSON.stringify(step.id)} references unknown formSchema field ${JSON.stringify(fname)}`,
|
|
189
|
+
path: ["steps"],
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const list = claims.get(fname) ?? [];
|
|
194
|
+
list.push(step.id);
|
|
195
|
+
claims.set(fname, list);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
for (const fname of fieldNames) {
|
|
199
|
+
const list = claims.get(fname) ?? [];
|
|
200
|
+
if (list.length === 0) {
|
|
201
|
+
// The LLM gets stuck here in a known loop: error #1 says "field not
|
|
202
|
+
// claimed", LLM removes the field, then `formSchema.fields.min(1)`
|
|
203
|
+
// fires and the LLM has no path forward (formSchema is non-optional
|
|
204
|
+
// and must have ≥1 field). Tell it the resolution explicitly: claim
|
|
205
|
+
// the field from a step. Reference the no-data pattern in the docs
|
|
206
|
+
// so the LLM can also pick the inverse (placeholder field claimed
|
|
207
|
+
// by the single step) when that fits the obligation better.
|
|
208
|
+
ctx.addIssue({
|
|
209
|
+
code: z.ZodIssueCode.custom,
|
|
210
|
+
message:
|
|
211
|
+
`formSchema field ${JSON.stringify(fname)} is not claimed by any step.fields[]. ` +
|
|
212
|
+
`Add ${JSON.stringify(fname)} to exactly one of the entries in steps[].fields[] ` +
|
|
213
|
+
`(every formSchema field must be claimed by one step). ` +
|
|
214
|
+
`If the obligation has nothing to record, see the "obligation with nothing to record" example in config/helps/encore-dsl.md ` +
|
|
215
|
+
`— you still need one placeholder field claimed by the step.`,
|
|
216
|
+
path: ["formSchema", "fields"],
|
|
217
|
+
});
|
|
218
|
+
} else if (list.length > 1) {
|
|
219
|
+
const owners = list.map((stepId) => JSON.stringify(stepId)).join(", ");
|
|
220
|
+
ctx.addIssue({
|
|
221
|
+
code: z.ZodIssueCode.custom,
|
|
222
|
+
message: `formSchema field ${JSON.stringify(fname)} is claimed by multiple steps: ${owners}`,
|
|
223
|
+
path: ["formSchema", "fields"],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function validateDefaultKeys(doc: Doc, fieldNames: Set<string>, ctx: Ctx): void {
|
|
230
|
+
for (const target of doc.targets) {
|
|
231
|
+
if (!target.defaults) continue;
|
|
232
|
+
for (const key of Object.keys(target.defaults)) {
|
|
233
|
+
if (!fieldNames.has(key)) {
|
|
234
|
+
ctx.addIssue({
|
|
235
|
+
code: z.ZodIssueCode.custom,
|
|
236
|
+
message: `target ${JSON.stringify(target.id)} default for ${JSON.stringify(key)} references unknown formSchema field`,
|
|
237
|
+
path: ["targets"],
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function validateFiringPlanOrder(doc: Doc, ctx: Ctx): void {
|
|
245
|
+
// Use a representative cycle (the actual date doesn't matter, only
|
|
246
|
+
// the relative ordering of phases against it).
|
|
247
|
+
const anchorsBase = { cycleStart: "2026-01-01", cycleDeadline: "2026-12-31" };
|
|
248
|
+
for (const step of doc.steps) {
|
|
249
|
+
let stepDeadlineIso: string | undefined;
|
|
250
|
+
try {
|
|
251
|
+
const expr = parseAtExpression(step.deadline, { allowStepDeadline: false });
|
|
252
|
+
stepDeadlineIso = resolveAtExpression(expr, anchorsBase);
|
|
253
|
+
} catch {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const anchors = { ...anchorsBase, stepDeadline: stepDeadlineIso };
|
|
257
|
+
let prev: string | null = null;
|
|
258
|
+
for (let i = 0; i < step.firingPlan.length; i++) {
|
|
259
|
+
const phase = step.firingPlan[i];
|
|
260
|
+
let resolved: string;
|
|
261
|
+
try {
|
|
262
|
+
const expr = parseAtExpression(phase.at, { allowStepDeadline: true });
|
|
263
|
+
resolved = resolveAtExpression(expr, anchors);
|
|
264
|
+
} catch {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (prev !== null && resolved < prev) {
|
|
268
|
+
ctx.addIssue({
|
|
269
|
+
code: z.ZodIssueCode.custom,
|
|
270
|
+
message: `step ${JSON.stringify(step.id)}: firingPlan[${i}].at ${JSON.stringify(phase.at)} resolves before the previous phase — phases must be chronologically ordered`,
|
|
271
|
+
path: ["steps"],
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
prev = resolved;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Top-level DSL union. Input shape — the post-superRefine resolved
|
|
280
|
+
* shape lives in `EncoreDsl` below. */
|
|
281
|
+
export const EncoreDslInput = z.discriminatedUnion("type", [PaymentDsl, ServiceDsl]).superRefine((doc, ctx) => {
|
|
282
|
+
const { fieldNames } = validateUniqueIds(doc, ctx);
|
|
283
|
+
validateFieldOwnership(doc, fieldNames, ctx);
|
|
284
|
+
validateDefaultKeys(doc, fieldNames, ctx);
|
|
285
|
+
validateFiringPlanOrder(doc, ctx);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
export type EncoreDsl = z.infer<typeof EncoreDslInput>;
|
|
@@ -69,7 +69,7 @@ export type NotificationPriority = (typeof NOTIFICATION_PRIORITIES)[keyof typeof
|
|
|
69
69
|
// router / page component needs to jump to a specific item. Omitting
|
|
70
70
|
// the identifier lands on the feature's index view.
|
|
71
71
|
export type NotificationTarget =
|
|
72
|
-
| { view: typeof NOTIFICATION_VIEWS.chat; sessionId: string
|
|
72
|
+
| { view: typeof NOTIFICATION_VIEWS.chat; sessionId: string }
|
|
73
73
|
| { view: typeof NOTIFICATION_VIEWS.todos; itemId?: string }
|
|
74
74
|
| { view: typeof NOTIFICATION_VIEWS.calendar }
|
|
75
75
|
| { view: typeof NOTIFICATION_VIEWS.automations; taskId?: string }
|
|
@@ -96,6 +96,7 @@ export type NotificationAction =
|
|
|
96
96
|
* client is on. Other notification kinds may opt in later. */
|
|
97
97
|
export interface NotificationI18n {
|
|
98
98
|
titleKey: string;
|
|
99
|
+
titleParams?: Readonly<Record<string, string | number | readonly string[]>>;
|
|
99
100
|
bodyKey?: string;
|
|
100
101
|
bodyParams?: Readonly<Record<string, string | number | readonly string[]>>;
|
|
101
102
|
}
|
package/src/types/session.ts
CHANGED
|
@@ -63,7 +63,13 @@ export interface SessionSummary {
|
|
|
63
63
|
isBookmarked?: boolean;
|
|
64
64
|
// Live state from the server session store (present when the
|
|
65
65
|
// session has an active in-memory entry on the server).
|
|
66
|
+
//
|
|
67
|
+
// `isRunning` — broad: agent turn live OR background generation
|
|
68
|
+
// pending. Drives the sidebar busy indicator.
|
|
69
|
+
// `liveIsRunning` — narrow: mirrors the DELETE 409 gate exactly
|
|
70
|
+
// (#1195). `false` ⇒ a DELETE on this session will be accepted.
|
|
66
71
|
isRunning?: boolean;
|
|
72
|
+
liveIsRunning?: boolean;
|
|
67
73
|
hasUnread?: boolean;
|
|
68
74
|
statusMessage?: string;
|
|
69
75
|
}
|
package/src/types/sse.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface SseToolCallResult {
|
|
|
17
17
|
type: typeof EVENT_TYPES.toolCallResult;
|
|
18
18
|
toolUseId: string;
|
|
19
19
|
content: string;
|
|
20
|
+
/** Set when the tool-result block carried `is_error: true` —
|
|
21
|
+
* forwarded from `AgentEvent.toolCallResult.isError` so the
|
|
22
|
+
* frontend can render the chip distinctly. Drives the MCP
|
|
23
|
+
* failure monitor (#1353). */
|
|
24
|
+
isError?: boolean;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
export interface SseStatus {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// session domain types, the pending-calls helper, etc.) can refer
|
|
4
4
|
// to it without depending on a Vue file.
|
|
5
5
|
|
|
6
|
+
import type { McpHint } from "../utils/agent/mcpHint";
|
|
7
|
+
|
|
6
8
|
export interface ToolCallHistoryItem {
|
|
7
9
|
toolUseId: string;
|
|
8
10
|
toolName: string;
|
|
@@ -10,4 +12,9 @@ export interface ToolCallHistoryItem {
|
|
|
10
12
|
timestamp: number;
|
|
11
13
|
result?: string;
|
|
12
14
|
error?: string;
|
|
15
|
+
/** Structured hint surfaced next to `error` when the failing tool
|
|
16
|
+
* belongs to a catalogued MCP server. Lets the right-sidebar
|
|
17
|
+
* render setup-guide links / required-key reminders without the
|
|
18
|
+
* caller re-parsing the tool name. (#1354) */
|
|
19
|
+
mcpHint?: McpHint;
|
|
13
20
|
}
|