daily-focus 1.2.0-dev.9 → 2.0.0-dev.15
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 +18 -4
- package/SETUP.md +156 -105
- package/dist/{audit-DNTFZOHH.js → audit-AQUBVWPA.js} +4 -3
- package/dist/{chunk-KZNY45XW.js → chunk-72NUDSOV.js} +126 -119
- package/dist/{chunk-GQ3NCWRT.js → chunk-ITUK7XKS.js} +6 -19
- package/dist/{chunk-KL6RU4XT.js → chunk-JTMBBUMD.js} +316 -10
- package/dist/chunk-QG4XXKBP.js +18 -0
- package/dist/chunk-U6EPOXEW.js +570 -0
- package/dist/{chunk-P3E2B2SD.js → chunk-WPXIQPYX.js} +1 -1
- package/dist/{chunk-6SMUSPZG.js → chunk-YPGKXTBX.js} +2 -2
- package/dist/cli.js +19 -12
- package/dist/{demo-TIIXODJG.js → demo-KE6WDWMA.js} +159 -37
- package/dist/{init-XDMAXU2S.js → init-5KJVHWJU.js} +41 -6
- package/dist/{seed-TWT7EDXJ.js → seed-T4VEY6DT.js} +4 -3
- package/dist/{server-WT4CQ3VX.js → server-FFRNYEAO.js} +2000 -743
- package/dist/{service-ZUHI3VKH.js → service-LQ46YRCZ.js} +7 -6
- package/package.json +6 -2
- package/prompts/README.md +20 -4
- package/prompts/assistant.md +7 -1
- package/prompts/morning-brief-personal.md +37 -0
- package/prompts/morning-brief-work.md +37 -0
- package/prompts/onboarding.md +41 -0
- package/public/app.js +806 -287
- package/public/fonts/IBMPlexSans-Variable.woff2 +0 -0
- package/public/fonts/IBMPlexSerif-Italic.woff2 +0 -0
- package/public/fonts/IBMPlexSerif-Medium.woff2 +0 -0
- package/public/fonts/IBMPlexSerif-Regular.woff2 +0 -0
- package/public/fonts/IBMPlexSerif-SemiBold.woff2 +0 -0
- package/public/fonts/OFL.txt +93 -0
- package/public/style.css +1686 -481
- package/dist/chunk-V2UN3JYL.js +0 -140
package/public/app.js
CHANGED
|
@@ -1110,6 +1110,24 @@ function postObjective(objective, blocker) {
|
|
|
1110
1110
|
function postListCalendars() {
|
|
1111
1111
|
return postJson("/api/calendars/list", {});
|
|
1112
1112
|
}
|
|
1113
|
+
function postSetupAnswer(step, answer) {
|
|
1114
|
+
return post("/api/onboarding/answer", { step, answer });
|
|
1115
|
+
}
|
|
1116
|
+
function postSetupRevise(step, text, draft) {
|
|
1117
|
+
return post("/api/onboarding/revise", { step, text, draft });
|
|
1118
|
+
}
|
|
1119
|
+
function postSetupDetect() {
|
|
1120
|
+
return post("/api/onboarding/detect");
|
|
1121
|
+
}
|
|
1122
|
+
function postSetupRetry(step) {
|
|
1123
|
+
return post("/api/onboarding/retry", { step });
|
|
1124
|
+
}
|
|
1125
|
+
function postSetupFinish() {
|
|
1126
|
+
return post("/api/onboarding/finish");
|
|
1127
|
+
}
|
|
1128
|
+
async function fetchSetupSources() {
|
|
1129
|
+
return jsonFrom(await fetch("/api/onboarding/sources"), "GET /api/onboarding/sources");
|
|
1130
|
+
}
|
|
1113
1131
|
|
|
1114
1132
|
// client/state.ts
|
|
1115
1133
|
var VIEWS = ["today", "board", "tickets", "settings"];
|
|
@@ -1144,7 +1162,14 @@ function createUi(initial = {}) {
|
|
|
1144
1162
|
settingsError: y3(initial.settingsError ?? null),
|
|
1145
1163
|
settingsSaving: y3(initial.settingsSaving ?? false),
|
|
1146
1164
|
texts: y3(initial.texts ?? {}),
|
|
1147
|
-
calendars: y3(initial.calendars ?? null)
|
|
1165
|
+
calendars: y3(initial.calendars ?? null),
|
|
1166
|
+
setupDrafts: y3(initial.setupDrafts ?? /* @__PURE__ */ new Map()),
|
|
1167
|
+
setupEditing: y3(initial.setupEditing ?? null),
|
|
1168
|
+
setupConfirm: y3(initial.setupConfirm ?? null),
|
|
1169
|
+
setupBusy: y3(initial.setupBusy ?? null),
|
|
1170
|
+
setupError: y3(initial.setupError ?? null),
|
|
1171
|
+
setupTell: y3(initial.setupTell ?? /* @__PURE__ */ new Map()),
|
|
1172
|
+
setupSources: y3(initial.setupSources ?? null)
|
|
1148
1173
|
};
|
|
1149
1174
|
}
|
|
1150
1175
|
function isView(value) {
|
|
@@ -1666,7 +1691,7 @@ async function loadSettings() {
|
|
|
1666
1691
|
showToast(`Could not load the settings: ${err.message}`);
|
|
1667
1692
|
}
|
|
1668
1693
|
}
|
|
1669
|
-
async function sendSettings(values
|
|
1694
|
+
async function sendSettings(values) {
|
|
1670
1695
|
if (ui.settingsSaving.value) return;
|
|
1671
1696
|
n2(() => {
|
|
1672
1697
|
ui.settingsSaving.value = true;
|
|
@@ -1676,17 +1701,14 @@ async function sendSettings(values, fromDraft) {
|
|
|
1676
1701
|
const page = await postSettings(values);
|
|
1677
1702
|
n2(() => {
|
|
1678
1703
|
ui.settings.value = page;
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
ui.settingsDraft.value = draft;
|
|
1683
|
-
}
|
|
1704
|
+
const draft = new Map(ui.settingsDraft.value);
|
|
1705
|
+
for (const [key, value] of Object.entries(values)) if (draft.get(key) === value) draft.delete(key);
|
|
1706
|
+
ui.settingsDraft.value = draft;
|
|
1684
1707
|
});
|
|
1685
1708
|
restartToast(page);
|
|
1686
1709
|
if (page.restart === "waiting") void followRestart();
|
|
1687
1710
|
} catch (err) {
|
|
1688
|
-
|
|
1689
|
-
else showToast(`Could not save: ${err.message}`);
|
|
1711
|
+
ui.settingsError.value = err.message;
|
|
1690
1712
|
} finally {
|
|
1691
1713
|
ui.settingsSaving.value = false;
|
|
1692
1714
|
}
|
|
@@ -1764,6 +1786,60 @@ async function listCalendars() {
|
|
|
1764
1786
|
ui.calendars.value = err.message;
|
|
1765
1787
|
}
|
|
1766
1788
|
}
|
|
1789
|
+
async function setupRequest(step, send) {
|
|
1790
|
+
if (ui.setupBusy.value) return false;
|
|
1791
|
+
n2(() => {
|
|
1792
|
+
ui.setupBusy.value = step;
|
|
1793
|
+
ui.setupError.value = null;
|
|
1794
|
+
});
|
|
1795
|
+
try {
|
|
1796
|
+
adoptState(await send());
|
|
1797
|
+
return true;
|
|
1798
|
+
} catch (err) {
|
|
1799
|
+
ui.setupError.value = { step, message: err.message };
|
|
1800
|
+
return false;
|
|
1801
|
+
} finally {
|
|
1802
|
+
ui.setupBusy.value = null;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
function forgetDraft(step) {
|
|
1806
|
+
if (!ui.setupDrafts.value.has(step)) return;
|
|
1807
|
+
const next = new Map(ui.setupDrafts.value);
|
|
1808
|
+
next.delete(step);
|
|
1809
|
+
ui.setupDrafts.value = next;
|
|
1810
|
+
}
|
|
1811
|
+
async function answerSetup(step, answer) {
|
|
1812
|
+
if (!await setupRequest(step, () => postSetupAnswer(step, answer))) return;
|
|
1813
|
+
n2(() => {
|
|
1814
|
+
forgetDraft(step);
|
|
1815
|
+
if (ui.setupEditing.value === step) ui.setupEditing.value = null;
|
|
1816
|
+
if (ui.setupConfirm.value === step) ui.setupConfirm.value = null;
|
|
1817
|
+
ui.setupSources.value = null;
|
|
1818
|
+
});
|
|
1819
|
+
afterRender(() => {
|
|
1820
|
+
const heading = document.querySelector('.onboarding__card[data-current="true"] h2');
|
|
1821
|
+
heading?.focus({ preventScroll: true });
|
|
1822
|
+
heading?.closest(".onboarding__card")?.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
async function reviseSetup(step, text, draft) {
|
|
1826
|
+
if (!await setupRequest(step, () => postSetupRevise(step, text, draft))) return;
|
|
1827
|
+
n2(() => {
|
|
1828
|
+
forgetDraft(step);
|
|
1829
|
+
if (ui.setupConfirm.value === step) ui.setupConfirm.value = null;
|
|
1830
|
+
const tell = new Map(ui.setupTell.value);
|
|
1831
|
+
tell.delete(step);
|
|
1832
|
+
ui.setupTell.value = tell;
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
async function loadSetupSources() {
|
|
1836
|
+
ui.setupSources.value = "loading";
|
|
1837
|
+
try {
|
|
1838
|
+
ui.setupSources.value = await fetchSetupSources();
|
|
1839
|
+
} catch (err) {
|
|
1840
|
+
ui.setupSources.value = { error: err.message };
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1767
1843
|
function openSettings(section2 = null) {
|
|
1768
1844
|
if (ui.view.value !== "settings") {
|
|
1769
1845
|
try {
|
|
@@ -1897,14 +1973,39 @@ var handlers = {
|
|
|
1897
1973
|
ui.settingsDraft.value = /* @__PURE__ */ new Map();
|
|
1898
1974
|
ui.settingsError.value = null;
|
|
1899
1975
|
}),
|
|
1900
|
-
saveSettings: (
|
|
1901
|
-
chooseSettings: (values) => void sendSettings(values, false),
|
|
1976
|
+
saveSettings: () => void sendSettings(Object.fromEntries(ui.settingsDraft.value)),
|
|
1902
1977
|
loadText: (name) => void loadText(name),
|
|
1903
1978
|
editText: (name, text) => patchText(name, { draft: text }),
|
|
1904
1979
|
saveText: (name, overwrite) => void saveText(name, overwrite),
|
|
1905
1980
|
reloadText: (name) => void loadText(name),
|
|
1906
1981
|
saveObjective: (objective, blocker) => void saveObjective(objective, blocker),
|
|
1907
|
-
listCalendars: () => void listCalendars()
|
|
1982
|
+
listCalendars: () => void listCalendars(),
|
|
1983
|
+
answerSetup: (step, answer) => void answerSetup(step, answer),
|
|
1984
|
+
editSetupDraft: (step, answer, version) => {
|
|
1985
|
+
if (answer === null) return forgetDraft(step);
|
|
1986
|
+
const next = new Map(ui.setupDrafts.value);
|
|
1987
|
+
next.set(step, { version, answer });
|
|
1988
|
+
ui.setupDrafts.value = next;
|
|
1989
|
+
},
|
|
1990
|
+
editSetupStep: (step) => n2(() => {
|
|
1991
|
+
ui.setupEditing.value = step;
|
|
1992
|
+
ui.setupError.value = null;
|
|
1993
|
+
}),
|
|
1994
|
+
confirmSetup: (step) => {
|
|
1995
|
+
ui.setupConfirm.value = step;
|
|
1996
|
+
},
|
|
1997
|
+
editSetupTell: (step, text) => {
|
|
1998
|
+
const next = new Map(ui.setupTell.value);
|
|
1999
|
+
next.set(step, text);
|
|
2000
|
+
ui.setupTell.value = next;
|
|
2001
|
+
},
|
|
2002
|
+
reviseSetup: (step, text, draft) => void reviseSetup(step, text, draft),
|
|
2003
|
+
retrySetup: (step) => void setupRequest(step, () => postSetupRetry(step)),
|
|
2004
|
+
detectSetup: () => void setupRequest("detect", () => postSetupDetect()),
|
|
2005
|
+
finishSetup: () => void setupRequest("finish", () => postSetupFinish()).then((finished) => {
|
|
2006
|
+
if (finished && state.value?.restart === "waiting") void followRestart();
|
|
2007
|
+
}),
|
|
2008
|
+
loadSetupSources: () => void loadSetupSources()
|
|
1908
2009
|
};
|
|
1909
2010
|
|
|
1910
2011
|
// client/format.ts
|
|
@@ -2378,10 +2479,11 @@ function agentRunBanner(run, state2, handlers2) {
|
|
|
2378
2479
|
}
|
|
2379
2480
|
};
|
|
2380
2481
|
if (run.status === "running") {
|
|
2482
|
+
const helping = (run.steps ?? []).filter((step) => step.subagent && step.state === "running").length;
|
|
2381
2483
|
return bannerNode(
|
|
2382
2484
|
"info",
|
|
2383
2485
|
"i",
|
|
2384
|
-
`The morning agent is writing a new brief, started ${relativeTime(run.startedAt, now)}
|
|
2486
|
+
`The morning agent is writing a new brief, started ${relativeTime(run.startedAt, now)}` + (helping ? `, with ${helping} ${helping === 1 ? "subagent" : "subagents"} at work.` : "."),
|
|
2385
2487
|
[el("button", { type: "button", class: "button banner__action", onClick: () => handlers2.stopAgent() }, "Stop")],
|
|
2386
2488
|
strip
|
|
2387
2489
|
);
|
|
@@ -2538,7 +2640,11 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2538
2640
|
},
|
|
2539
2641
|
el(
|
|
2540
2642
|
"span",
|
|
2541
|
-
|
|
2643
|
+
// A ranked row's number stands in the margin where the source dot would be:
|
|
2644
|
+
// the stylesheet draws it off `data-rank` and hides the dot, since the
|
|
2645
|
+
// source is named in the meta line anyway and the rank is what this row
|
|
2646
|
+
// is for.
|
|
2647
|
+
{ class: "item__mark", "data-rank": item.priority !== void 0 && item.status === "open" ? String(item.priority) : void 0 },
|
|
2542
2648
|
el("span", {
|
|
2543
2649
|
class: "item__dot",
|
|
2544
2650
|
style: `background:${sourceColor(item.source)}`,
|
|
@@ -2550,7 +2656,8 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2550
2656
|
{ class: "item__body" },
|
|
2551
2657
|
el("p", { class: "item__title" }, title),
|
|
2552
2658
|
renderMeta(item, state2, now),
|
|
2553
|
-
item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null
|
|
2659
|
+
item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null,
|
|
2660
|
+
renderAhead(item.id, state2)
|
|
2554
2661
|
),
|
|
2555
2662
|
renderActions(item, ui2, handlers2),
|
|
2556
2663
|
ui2.menuFor.value === item.id ? renderSnoozeMenu(item, now, handlers2) : null
|
|
@@ -2563,9 +2670,6 @@ function renderMeta(item, state2, now) {
|
|
|
2563
2670
|
pills.push(el("span", { class: cls }, text));
|
|
2564
2671
|
said.add(text.trim().toLowerCase());
|
|
2565
2672
|
};
|
|
2566
|
-
if (item.priority !== void 0 && item.status === "open") {
|
|
2567
|
-
pill("pill pill--priority", `#${item.priority}`);
|
|
2568
|
-
}
|
|
2569
2673
|
for (const [cls, text] of rowPillTexts(item, state2)) pill(cls, text);
|
|
2570
2674
|
if (item.advancesObjective) {
|
|
2571
2675
|
pill("pill pill--objective", "objective");
|
|
@@ -2614,6 +2718,38 @@ function rowPillTexts(row, state2) {
|
|
|
2614
2718
|
function rowPills(row, state2) {
|
|
2615
2719
|
return rowPillTexts(row, state2).map(([cls, text]) => el("span", { class: cls }, text));
|
|
2616
2720
|
}
|
|
2721
|
+
var READING = {
|
|
2722
|
+
"assess-review": "Reading the review\u2026",
|
|
2723
|
+
"explain-ci": "Reading the failing checks\u2026"
|
|
2724
|
+
};
|
|
2725
|
+
function renderAhead(id, state2) {
|
|
2726
|
+
const auto = state2.assistant?.auto;
|
|
2727
|
+
const keys = auto?.rows?.[id];
|
|
2728
|
+
if (!auto || !keys?.length) return null;
|
|
2729
|
+
const turn = (state2.assistant.items?.[id]?.turns ?? []).findLast((candidate) => candidate.auto !== null && keys.includes(candidate.auto));
|
|
2730
|
+
const mark = el("span", { class: "item__ahead-mark", "aria-hidden": "true" }, "\u2726");
|
|
2731
|
+
if (!turn) {
|
|
2732
|
+
return auto.enabled && auto.usedToday >= auto.limit ? el("p", { class: "item__ahead item__ahead--quiet" }, mark, `Not answered ahead: today's ${auto.limit} are used.`) : null;
|
|
2733
|
+
}
|
|
2734
|
+
if (turn.status === "running") {
|
|
2735
|
+
return el("p", { class: "item__ahead item__ahead--quiet", "data-status": "running" }, mark, READING[turn.action ?? ""] ?? "Reading\u2026");
|
|
2736
|
+
}
|
|
2737
|
+
if (turn.status === "failed") {
|
|
2738
|
+
return el("p", { class: "item__ahead item__ahead--quiet" }, mark, "Could not answer ahead; the panel says why.");
|
|
2739
|
+
}
|
|
2740
|
+
const verdict = turn.status === "done" ? verdictLine(turn.reply) : "";
|
|
2741
|
+
if (!verdict) return null;
|
|
2742
|
+
return el(
|
|
2743
|
+
"p",
|
|
2744
|
+
{ class: "item__ahead", title: `Answered ahead ${relativeTime(turn.endedAt ?? turn.startedAt, new Date(state2.now))}. Open the panel for the whole answer.` },
|
|
2745
|
+
mark,
|
|
2746
|
+
renderMarkdown(verdict)
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
function verdictLine(reply) {
|
|
2750
|
+
const line = reply.split("\n").find((candidate) => candidate.trim() !== "") ?? "";
|
|
2751
|
+
return line.replace(/^\s*(?:#{1,6}\s+|>\s*|[-*•]\s+|\d+[.)]\s+)/, "").trim();
|
|
2752
|
+
}
|
|
2617
2753
|
function renderActions(item, ui2, handlers2) {
|
|
2618
2754
|
const buttons = [];
|
|
2619
2755
|
const act = (id, action) => () => handlers2.onAction(id, action);
|
|
@@ -2907,7 +3043,8 @@ function PullRow({ item: row, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2907
3043
|
),
|
|
2908
3044
|
renderPullMeta(row, state2, now),
|
|
2909
3045
|
renderPullReasons(row, now),
|
|
2910
|
-
renderCancelledChecks(row)
|
|
3046
|
+
renderCancelledChecks(row),
|
|
3047
|
+
renderAhead(row.id, state2)
|
|
2911
3048
|
),
|
|
2912
3049
|
renderPullActions(row, ui2, handlers2),
|
|
2913
3050
|
ui2.menuFor.value === row.id ? renderSnoozeMenu(row, now, handlers2, { indefinite: false }) : null
|
|
@@ -3800,6 +3937,7 @@ function describeWhen(value, now) {
|
|
|
3800
3937
|
return `${named ? day : date && date.getFullYear() === now.getFullYear() ? formatShortDay(value) : formatDay(value)} at ${formatTime(value)}`;
|
|
3801
3938
|
}
|
|
3802
3939
|
function runReport(run, ui2, handlers2) {
|
|
3940
|
+
if (run.steps?.length) return runWork(run, ui2, handlers2);
|
|
3803
3941
|
const messages = run.messages ?? (run.report ? [run.report] : []);
|
|
3804
3942
|
const running = run.status === "running";
|
|
3805
3943
|
const earlier = !running && messages.length && messages.at(-1) === run.report ? messages.slice(0, -1) : messages;
|
|
@@ -3858,6 +3996,92 @@ function runReport(run, ui2, handlers2) {
|
|
|
3858
3996
|
) : null
|
|
3859
3997
|
);
|
|
3860
3998
|
}
|
|
3999
|
+
function runWork(run, ui2, handlers2) {
|
|
4000
|
+
const running = run.status === "running";
|
|
4001
|
+
if (running) {
|
|
4002
|
+
return el(
|
|
4003
|
+
"section",
|
|
4004
|
+
{ class: "flyout__section" },
|
|
4005
|
+
el("h3", { class: "flyout__label" }, "What it is doing"),
|
|
4006
|
+
runSteps(run, run.steps, null, ui2, handlers2)
|
|
4007
|
+
);
|
|
4008
|
+
}
|
|
4009
|
+
const last = run.steps.findLast((step) => step.by === null && step.kind === "message");
|
|
4010
|
+
const steps2 = last && sameWords(last.text, run.report) ? run.steps.filter((step) => step !== last) : run.steps;
|
|
4011
|
+
const handoffs = steps2.filter((step) => step.subagent).length;
|
|
4012
|
+
return el(
|
|
4013
|
+
"section",
|
|
4014
|
+
{ class: "flyout__section" },
|
|
4015
|
+
el("h3", { class: "flyout__label" }, run.status === "done" ? "Its report" : "What it said last"),
|
|
4016
|
+
run.report ? el("div", { class: "assistant__reply" }, renderMarkdownBlocks(run.report)) : el("p", { class: "flyout__empty" }, "It said nothing."),
|
|
4017
|
+
steps2.length ? el(
|
|
4018
|
+
"details",
|
|
4019
|
+
{ class: "agent-run__list" },
|
|
4020
|
+
el(
|
|
4021
|
+
"summary",
|
|
4022
|
+
null,
|
|
4023
|
+
`How it got there, in ${steps2.length} ${steps2.length === 1 ? "step" : "steps"}` + (handoffs ? `, with ${handoffs} ${handoffs === 1 ? "subagent" : "subagents"}` : "")
|
|
4024
|
+
),
|
|
4025
|
+
runSteps(run, steps2, null, ui2, handlers2)
|
|
4026
|
+
) : null
|
|
4027
|
+
);
|
|
4028
|
+
}
|
|
4029
|
+
var NESTING = 4;
|
|
4030
|
+
function runSteps(run, steps2, by, ui2, handlers2, depth = 0) {
|
|
4031
|
+
const own = steps2.filter((step) => step.by === by);
|
|
4032
|
+
return el(
|
|
4033
|
+
"ol",
|
|
4034
|
+
{ class: "assistant__steps agent-run__steps" },
|
|
4035
|
+
own.length ? null : el("li", { class: "assistant__step assistant__step--message" }, "Nothing yet."),
|
|
4036
|
+
own.map((step, index) => {
|
|
4037
|
+
if (step.kind === "message") {
|
|
4038
|
+
return el("li", { key: index, class: "agent-run__said" }, renderMarkdownBlocks(step.text));
|
|
4039
|
+
}
|
|
4040
|
+
if (step.subagent && depth < NESTING) return runHandoff(run, steps2, step, ui2, handlers2, depth, index);
|
|
4041
|
+
return el(
|
|
4042
|
+
"li",
|
|
4043
|
+
{ key: index, class: "assistant__step", "data-state": step.state },
|
|
4044
|
+
el("span", { class: "assistant__step-text" }, step.text),
|
|
4045
|
+
step.detail ? el("code", { class: "assistant__step-detail", title: step.detail }, step.detail) : null
|
|
4046
|
+
);
|
|
4047
|
+
})
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
function runHandoff(run, steps2, step, ui2, handlers2, depth, index) {
|
|
4051
|
+
const id = step.subagent;
|
|
4052
|
+
const own = steps2.filter((candidate) => candidate.by === id);
|
|
4053
|
+
const latest = own.at(-1);
|
|
4054
|
+
const calls = own.filter((candidate) => candidate.kind === "tool").length;
|
|
4055
|
+
const doing = step.state === "running" ? latest ? latest.kind === "message" ? firstLine(latest.text) : latest.text : "starting" : `${calls} ${calls === 1 ? "step" : "steps"}`;
|
|
4056
|
+
const key = `${run.id}:${id}`;
|
|
4057
|
+
const fallback = step.state === "running";
|
|
4058
|
+
const open = ui2.messageOpen.value.has(key) ? ui2.messageOpen.value.get(key) : fallback;
|
|
4059
|
+
return el(
|
|
4060
|
+
"li",
|
|
4061
|
+
{ key: index, class: "assistant__step agent-run__handoff", "data-state": step.state },
|
|
4062
|
+
el(
|
|
4063
|
+
"details",
|
|
4064
|
+
{
|
|
4065
|
+
open,
|
|
4066
|
+
// As the message folds do: a toggle back to the default is forgotten.
|
|
4067
|
+
onToggle: (event) => {
|
|
4068
|
+
const isOpen = event.currentTarget.open;
|
|
4069
|
+
handlers2.toggleMessage(key, isOpen === fallback ? null : isOpen);
|
|
4070
|
+
}
|
|
4071
|
+
},
|
|
4072
|
+
el(
|
|
4073
|
+
"summary",
|
|
4074
|
+
null,
|
|
4075
|
+
el("span", { class: "assistant__step-text" }, `Subagent: ${step.detail ?? "unnamed"}`),
|
|
4076
|
+
el("span", { class: "assistant__step-detail" }, doing)
|
|
4077
|
+
),
|
|
4078
|
+
runSteps(run, steps2, id, ui2, handlers2, depth + 1)
|
|
4079
|
+
)
|
|
4080
|
+
);
|
|
4081
|
+
}
|
|
4082
|
+
function sameWords(a4, b4) {
|
|
4083
|
+
return a4.replace(/\s+/g, " ").trim() === b4.replace(/\s+/g, " ").trim();
|
|
4084
|
+
}
|
|
3861
4085
|
function runStatus(run, now, handlers2) {
|
|
3862
4086
|
if (run.status === "running") {
|
|
3863
4087
|
return el(
|
|
@@ -4225,6 +4449,7 @@ function renderAssistantTurn(turn, label, handlers2) {
|
|
|
4225
4449
|
const action = turn.action ? label.get(turn.action) : null;
|
|
4226
4450
|
const extra = action && turn.request.startsWith(action.request) ? turn.request.slice(action.request.length).trim() : null;
|
|
4227
4451
|
const asked = action ? [el("strong", null, action.label), extra ? ` \u2014 ${extra}` : ""] : [turn.request];
|
|
4452
|
+
if (turn.auto) asked.push(el("span", { class: "assistant__auto", title: "The dashboard asked this on its own, when the pull request started waiting on you." }, " \xB7 asked ahead"));
|
|
4228
4453
|
const { body, created } = turn.status === "done" ? splitCreated(turn.reply) : { body: turn.reply, created: [] };
|
|
4229
4454
|
const status = turn.status === "failed" ? el("p", { class: "assistant__status assistant__status--bad" }, `Failed: ${turn.error}`) : turn.status === "aborted" ? el("p", { class: "assistant__status" }, `Stopped: ${turn.error}`) : null;
|
|
4230
4455
|
return el(
|
|
@@ -4245,35 +4470,35 @@ function renderAssistantTurn(turn, label, handlers2) {
|
|
|
4245
4470
|
);
|
|
4246
4471
|
}
|
|
4247
4472
|
function renderWork(turn) {
|
|
4248
|
-
const
|
|
4473
|
+
const steps2 = turn.steps ?? [];
|
|
4249
4474
|
if (turn.status === "running") {
|
|
4250
4475
|
return el(
|
|
4251
4476
|
"div",
|
|
4252
4477
|
{ class: "assistant__work" },
|
|
4253
4478
|
el("p", { class: "assistant__status assistant__status--working" }, "Working \xB7 ", k(Elapsed, { since: turn.startedAt })),
|
|
4254
|
-
|
|
4479
|
+
steps2.length ? renderSteps(steps2) : null
|
|
4255
4480
|
);
|
|
4256
4481
|
}
|
|
4257
4482
|
const took = durationBetween(turn.startedAt, turn.endedAt);
|
|
4258
|
-
if (!
|
|
4259
|
-
const failed =
|
|
4483
|
+
if (!steps2.length) return took ? el("p", { class: "assistant__took" }, `Answered in ${took}`) : null;
|
|
4484
|
+
const failed = steps2.filter((step) => step.state === "failed").length;
|
|
4260
4485
|
const summary = [
|
|
4261
4486
|
took ? `${turn.status === "done" ? "Worked" : "Ran"} for ${took}` : null,
|
|
4262
|
-
`${
|
|
4487
|
+
`${steps2.length} ${steps2.length === 1 ? "step" : "steps"}`,
|
|
4263
4488
|
failed ? `${failed} failed` : null
|
|
4264
4489
|
].filter(Boolean).join(" \xB7 ");
|
|
4265
|
-
return el("details", { class: "assistant__work", open: turn.status === "failed" }, el("summary", null, summary), renderSteps(
|
|
4490
|
+
return el("details", { class: "assistant__work", open: turn.status === "failed" }, el("summary", null, summary), renderSteps(steps2));
|
|
4266
4491
|
}
|
|
4267
|
-
function renderSteps(
|
|
4492
|
+
function renderSteps(steps2) {
|
|
4268
4493
|
return el(
|
|
4269
4494
|
"ol",
|
|
4270
4495
|
{ class: "assistant__steps" },
|
|
4271
|
-
|
|
4272
|
-
(
|
|
4496
|
+
steps2.map(
|
|
4497
|
+
(step, index) => step.kind === "message" ? el("li", { key: index, class: "assistant__step assistant__step--message" }, renderMarkdown(step.text.trim())) : el(
|
|
4273
4498
|
"li",
|
|
4274
|
-
{ key: index, class: "assistant__step", "data-state":
|
|
4275
|
-
el("span", { class: "assistant__step-text" },
|
|
4276
|
-
|
|
4499
|
+
{ key: index, class: "assistant__step", "data-state": step.state },
|
|
4500
|
+
el("span", { class: "assistant__step-text" }, step.text),
|
|
4501
|
+
step.detail ? el("code", { class: "assistant__step-detail", title: step.detail }, step.detail) : null
|
|
4277
4502
|
)
|
|
4278
4503
|
)
|
|
4279
4504
|
);
|
|
@@ -4299,6 +4524,430 @@ function splitCreated(reply) {
|
|
|
4299
4524
|
return { body: created.length ? kept.join("\n").trim() : reply, created };
|
|
4300
4525
|
}
|
|
4301
4526
|
|
|
4527
|
+
// client/onboarding.ts
|
|
4528
|
+
var SECONDS_PER_STEP = 20;
|
|
4529
|
+
function OnboardingView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
4530
|
+
const flow = state2.onboarding;
|
|
4531
|
+
const open = flow.steps.filter((step) => step.status === "open");
|
|
4532
|
+
const editing = ui2.setupEditing.value;
|
|
4533
|
+
h2(() => {
|
|
4534
|
+
if (!flow.detected) handlers2.detectSetup();
|
|
4535
|
+
}, [flow.detected]);
|
|
4536
|
+
return el(
|
|
4537
|
+
"div",
|
|
4538
|
+
{ class: "onboarding" },
|
|
4539
|
+
el(
|
|
4540
|
+
"header",
|
|
4541
|
+
{ class: "onboarding__bar" },
|
|
4542
|
+
el("span", { class: "onboarding__name" }, "Daily Focus"),
|
|
4543
|
+
progress(flow)
|
|
4544
|
+
),
|
|
4545
|
+
el(
|
|
4546
|
+
"main",
|
|
4547
|
+
{ class: "onboarding__column", id: "main" },
|
|
4548
|
+
flow.steps.length > 0 && flow.steps.every((step) => step.status === "open") && flow.steps[0].id === flow.current ? el(
|
|
4549
|
+
"p",
|
|
4550
|
+
{ class: "onboarding__lede" },
|
|
4551
|
+
"Each morning an agent reads your mail, calendar, pull requests and the rest, and writes a short brief of what needs doing today. A few questions get it ready; most are answered already, so confirm or change them."
|
|
4552
|
+
) : null,
|
|
4553
|
+
el(
|
|
4554
|
+
"ol",
|
|
4555
|
+
{ class: "onboarding__thread" },
|
|
4556
|
+
flow.steps.map((step) => {
|
|
4557
|
+
if (step.id === flow.current || step.id === editing) {
|
|
4558
|
+
return el("li", { key: step.id }, k(StepCard, { step, ui: ui2, handlers: handlers2, current: step.id === flow.current }));
|
|
4559
|
+
}
|
|
4560
|
+
if (step.status === "open") return null;
|
|
4561
|
+
return el("li", { key: step.id }, doneRow(step, handlers2, ui2.setupBusy.value !== null));
|
|
4562
|
+
}),
|
|
4563
|
+
flow.current === null ? el("li", { key: "finish" }, k(FinishCard, { flow, ui: ui2, handlers: handlers2 })) : ahead(flow, open)
|
|
4564
|
+
)
|
|
4565
|
+
)
|
|
4566
|
+
);
|
|
4567
|
+
}
|
|
4568
|
+
function progress(flow) {
|
|
4569
|
+
const total = flow.steps.length + 1;
|
|
4570
|
+
const left = flow.steps.filter((step) => step.status === "open").length;
|
|
4571
|
+
const seconds = left * SECONDS_PER_STEP;
|
|
4572
|
+
const text = flow.current === null ? "Ready when you are" : seconds < 60 ? "Under a minute left" : seconds < 120 ? "About a minute left" : `About ${Math.round(seconds / 60)} minutes left`;
|
|
4573
|
+
const done = total - left - (flow.current === null ? 0 : 1);
|
|
4574
|
+
return el(
|
|
4575
|
+
"div",
|
|
4576
|
+
{ class: "onboarding__progress" },
|
|
4577
|
+
el(
|
|
4578
|
+
"div",
|
|
4579
|
+
{ class: "onboarding__track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": total, "aria-valuenow": done, "aria-label": "Setup" },
|
|
4580
|
+
el("i", { style: { width: `${Math.round(done / total * 100)}%` } })
|
|
4581
|
+
),
|
|
4582
|
+
el("span", null, text)
|
|
4583
|
+
);
|
|
4584
|
+
}
|
|
4585
|
+
function doneRow(step, handlers2, busy) {
|
|
4586
|
+
return el(
|
|
4587
|
+
"div",
|
|
4588
|
+
{ class: "onboarding__done", "data-status": step.status, "data-step": step.id },
|
|
4589
|
+
el("span", { class: "onboarding__dot", "aria-hidden": "true" }),
|
|
4590
|
+
el("span", { class: "onboarding__summary" }, step.summary ?? `${step.topic[0].toUpperCase()}${step.topic.slice(1)}: decide later.`),
|
|
4591
|
+
el("button", { type: "button", class: "onboarding__change", disabled: busy, onClick: () => handlers2.editSetupStep(step.id) }, "Change")
|
|
4592
|
+
);
|
|
4593
|
+
}
|
|
4594
|
+
function ahead(flow, open) {
|
|
4595
|
+
const rest = open.filter((step) => step.id !== flow.current);
|
|
4596
|
+
const looking = rest.find((step) => step.proposal?.status === "working");
|
|
4597
|
+
const topics = rest.filter((step) => step !== looking).map((step) => step.topic);
|
|
4598
|
+
const later = topics.length > 4 ? [...topics.slice(0, 3), `${topics.length - 3} more`] : topics;
|
|
4599
|
+
return el(
|
|
4600
|
+
"li",
|
|
4601
|
+
{ key: "ahead", class: "onboarding__ahead" },
|
|
4602
|
+
looking ? el(
|
|
4603
|
+
"div",
|
|
4604
|
+
{ class: "onboarding__next" },
|
|
4605
|
+
el("span", { class: "onboarding__dot onboarding__dot--looking", "aria-hidden": "true" }),
|
|
4606
|
+
el("p", null, `Next, ${looking.topic}. It's already looking:`),
|
|
4607
|
+
steps(looking)
|
|
4608
|
+
) : null,
|
|
4609
|
+
el(
|
|
4610
|
+
"p",
|
|
4611
|
+
{ class: "onboarding__then" },
|
|
4612
|
+
el("span", { class: "onboarding__dot onboarding__dot--later", "aria-hidden": "true" }),
|
|
4613
|
+
later.length ? `Then ${listed([...later, "your first brief"])}.` : "Then your first brief."
|
|
4614
|
+
)
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
function steps(step, count = 3) {
|
|
4618
|
+
const shown = (step.proposal?.steps ?? []).filter((s4) => s4.kind === "tool").slice(-count);
|
|
4619
|
+
const waiting = step.proposal?.status === "working" && !shown.some((s4) => s4.state === "running");
|
|
4620
|
+
return el(
|
|
4621
|
+
"ul",
|
|
4622
|
+
{ class: "onboarding__steps" },
|
|
4623
|
+
shown.map((s4, index) => el("li", { key: index, "data-state": s4.state, title: s4.detail ?? void 0 }, s4.text)),
|
|
4624
|
+
waiting ? el("li", { key: "waiting", "data-state": "running" }, shown.length ? "Writing its answer\u2026" : "Starting\u2026") : null
|
|
4625
|
+
);
|
|
4626
|
+
}
|
|
4627
|
+
function listed(names) {
|
|
4628
|
+
if (names.length <= 1) return names.join("");
|
|
4629
|
+
return `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
4630
|
+
}
|
|
4631
|
+
function baseAnswer(step) {
|
|
4632
|
+
if (step.answer && step.answer.later !== true) return step.answer;
|
|
4633
|
+
return {
|
|
4634
|
+
choices: Object.fromEntries(step.card.choices.map((choice) => [choice.id, choice.selected])),
|
|
4635
|
+
fields: Object.fromEntries(step.card.fields.map((field) => [field.id, field.value]))
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
function StepCard({ step, ui: ui2, handlers: handlers2, current }) {
|
|
4639
|
+
const saved = ui2.setupDrafts.value.get(step.id);
|
|
4640
|
+
const draft = saved?.answer ?? baseAnswer(step);
|
|
4641
|
+
const stale = saved !== void 0 && saved.version !== step.card.version;
|
|
4642
|
+
const busy = ui2.setupBusy.value !== null;
|
|
4643
|
+
const error = ui2.setupError.value?.step === step.id ? ui2.setupError.value.message : null;
|
|
4644
|
+
const editing = ui2.setupEditing.value === step.id;
|
|
4645
|
+
const proposal = step.proposal;
|
|
4646
|
+
const looking = proposal?.status === "working";
|
|
4647
|
+
const edit = (next) => handlers2.editSetupDraft(step.id, next, saved?.version ?? step.card.version);
|
|
4648
|
+
const tell = ui2.setupTell.value.get(step.id) ?? "";
|
|
4649
|
+
const empty = step.card.choices.every((choice) => choice.options.length === 0) && step.card.fields.length === 0;
|
|
4650
|
+
const conversation = proposal?.conversation ?? [];
|
|
4651
|
+
const revising = conversation.at(-1)?.from === "you";
|
|
4652
|
+
const confirming = looking && ui2.setupConfirm.value === step.id;
|
|
4653
|
+
const held = busy ? "Saving your last answer\u2026" : empty && looking ? "Still looking; this fills in when it is done" : empty && step.card.problem?.blocking ? "Fix what it says above, or decide later" : null;
|
|
4654
|
+
return el(
|
|
4655
|
+
"section",
|
|
4656
|
+
{ class: "onboarding__card", "data-current": String(current), "data-step": step.id, "aria-labelledby": `setup-${step.id}` },
|
|
4657
|
+
el("span", { class: "onboarding__dot onboarding__dot--open", "aria-hidden": "true" }),
|
|
4658
|
+
el("h2", { id: `setup-${step.id}`, tabIndex: -1 }, step.title),
|
|
4659
|
+
conversation.length ? el(
|
|
4660
|
+
"ol",
|
|
4661
|
+
{ class: "onboarding__talk" },
|
|
4662
|
+
conversation.map(
|
|
4663
|
+
(line, index) => line.from === "it" ? el("li", { key: index, class: "onboarding__why" }, sparkle(), el("span", null, line.text)) : el("li", { key: index, class: "onboarding__said" }, line.text)
|
|
4664
|
+
)
|
|
4665
|
+
) : step.card.why && !(looking && empty) ? el("p", { class: "onboarding__why" }, sparkle(), el("span", null, step.card.why)) : null,
|
|
4666
|
+
looking ? el(
|
|
4667
|
+
"div",
|
|
4668
|
+
{ class: "onboarding__looking", role: "status" },
|
|
4669
|
+
revising ? null : el("p", null, empty ? `Looking into ${step.topic}\u2026` : "Still looking. You can answer now, or wait for its pick."),
|
|
4670
|
+
steps(step)
|
|
4671
|
+
) : null,
|
|
4672
|
+
// Under what it said, so a reply reads as one, and what comes back lands here too.
|
|
4673
|
+
step.card.revisable && !looking ? el(
|
|
4674
|
+
"form",
|
|
4675
|
+
{
|
|
4676
|
+
class: "onboarding__tell",
|
|
4677
|
+
onSubmit: (event) => {
|
|
4678
|
+
event.preventDefault();
|
|
4679
|
+
if (tell.trim() && !busy) handlers2.reviseSetup(step.id, tell, draft);
|
|
4680
|
+
}
|
|
4681
|
+
},
|
|
4682
|
+
el("input", {
|
|
4683
|
+
type: "text",
|
|
4684
|
+
value: tell,
|
|
4685
|
+
"aria-label": step.card.reply ?? "Tell it what to change",
|
|
4686
|
+
placeholder: step.card.reply ?? "Tell it what to change",
|
|
4687
|
+
onInput: (event) => handlers2.editSetupTell(step.id, event.currentTarget.value)
|
|
4688
|
+
}),
|
|
4689
|
+
el("button", { type: "submit", class: "button", disabled: busy || tell.trim() === "" }, "Send")
|
|
4690
|
+
) : null,
|
|
4691
|
+
proposal?.status === "failed" ? el(
|
|
4692
|
+
"p",
|
|
4693
|
+
{ class: "onboarding__note" },
|
|
4694
|
+
`It couldn't look into this: ${proposal.error}. `,
|
|
4695
|
+
el("button", { type: "button", class: "link-button", disabled: busy, onClick: () => handlers2.retrySetup(step.id) }, "Try again")
|
|
4696
|
+
) : null,
|
|
4697
|
+
stale ? el(
|
|
4698
|
+
"p",
|
|
4699
|
+
{ class: "onboarding__note" },
|
|
4700
|
+
"It has a new suggestion since you started changing this. ",
|
|
4701
|
+
el("button", { type: "button", class: "link-button", onClick: () => handlers2.editSetupDraft(step.id, null, 0) }, "Use it")
|
|
4702
|
+
) : null,
|
|
4703
|
+
step.card.problem?.blocking ? problem(step, busy, handlers2) : null,
|
|
4704
|
+
el(
|
|
4705
|
+
"form",
|
|
4706
|
+
{
|
|
4707
|
+
class: "onboarding__form",
|
|
4708
|
+
onSubmit: (event) => {
|
|
4709
|
+
event.preventDefault();
|
|
4710
|
+
if (busy) return;
|
|
4711
|
+
if (looking && !confirming) return handlers2.confirmSetup(step.id);
|
|
4712
|
+
handlers2.answerSetup(step.id, draft);
|
|
4713
|
+
}
|
|
4714
|
+
},
|
|
4715
|
+
// Fields first: a card with both asks for the few words before the list.
|
|
4716
|
+
step.card.fields.length ? el(
|
|
4717
|
+
"div",
|
|
4718
|
+
{ class: "onboarding__fields" },
|
|
4719
|
+
step.card.fields.map(
|
|
4720
|
+
(field) => fieldView(field, draft.fields[field.id] ?? "", (value) => edit({ ...draft, fields: { ...draft.fields, [field.id]: value } }))
|
|
4721
|
+
)
|
|
4722
|
+
) : null,
|
|
4723
|
+
step.card.choices.map(
|
|
4724
|
+
(choice) => k(ChoiceView, {
|
|
4725
|
+
choice,
|
|
4726
|
+
picked: draft.choices[choice.id] ?? [],
|
|
4727
|
+
onPick: (values) => edit({ ...draft, choices: { ...draft.choices, [choice.id]: values } })
|
|
4728
|
+
})
|
|
4729
|
+
),
|
|
4730
|
+
step.card.problem && !step.card.problem.blocking ? problem(step, busy, handlers2) : null,
|
|
4731
|
+
error ? el("p", { class: "onboarding__error", role: "alert" }, error) : null,
|
|
4732
|
+
confirming ? el(
|
|
4733
|
+
"div",
|
|
4734
|
+
{ class: "onboarding__actions onboarding__confirm", role: "alert" },
|
|
4735
|
+
el("p", null, revising ? "It\u2019s still working on your change." : "It\u2019s still filling this in.", " Continue with what\u2019s here, and stop it?"),
|
|
4736
|
+
el("button", { type: "submit", class: "button button--primary", disabled: busy }, "Continue without it"),
|
|
4737
|
+
el("button", { type: "button", class: "button onboarding__quiet", onClick: () => handlers2.confirmSetup(null) }, "Wait for it")
|
|
4738
|
+
) : el(
|
|
4739
|
+
"div",
|
|
4740
|
+
{ class: "onboarding__actions" },
|
|
4741
|
+
el(
|
|
4742
|
+
"button",
|
|
4743
|
+
{ type: "submit", class: "button button--primary", disabled: held !== null, title: held ?? void 0 },
|
|
4744
|
+
ui2.setupBusy.value === step.id ? "Saving\u2026" : step.card.empty && step.card.fields.every((field) => !(draft.fields[field.id] ?? "").trim()) ? step.card.empty : "Continue"
|
|
4745
|
+
),
|
|
4746
|
+
step.card.later ? el(
|
|
4747
|
+
"button",
|
|
4748
|
+
{ type: "button", class: "button onboarding__quiet", disabled: busy, onClick: () => handlers2.answerSetup(step.id, { later: true }) },
|
|
4749
|
+
step.card.later
|
|
4750
|
+
) : null,
|
|
4751
|
+
editing ? el("button", { type: "button", class: "button onboarding__quiet", onClick: () => handlers2.editSetupStep(null) }, "Cancel") : null
|
|
4752
|
+
)
|
|
4753
|
+
)
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4756
|
+
function problem(step, busy, handlers2) {
|
|
4757
|
+
const found = step.card.problem;
|
|
4758
|
+
return el(
|
|
4759
|
+
"div",
|
|
4760
|
+
{ class: "onboarding__problem", "data-blocking": String(found.blocking) },
|
|
4761
|
+
el("p", null, found.message),
|
|
4762
|
+
found.links?.length ? el(
|
|
4763
|
+
"p",
|
|
4764
|
+
{ class: "onboarding__links" },
|
|
4765
|
+
found.links.map((link) => el("a", { key: link.url, href: link.url, target: "_blank", rel: "noopener" }, link.label))
|
|
4766
|
+
) : null,
|
|
4767
|
+
found.command ? el("pre", null, el("code", null, found.command)) : null,
|
|
4768
|
+
el("button", { type: "button", class: "button", disabled: busy, onClick: () => handlers2.retrySetup(step.id) }, "Check again")
|
|
4769
|
+
);
|
|
4770
|
+
}
|
|
4771
|
+
function sparkle() {
|
|
4772
|
+
return el(
|
|
4773
|
+
"svg",
|
|
4774
|
+
{ class: "onboarding__sparkle", width: 14, height: 14, viewBox: "0 0 14 14", "aria-hidden": "true" },
|
|
4775
|
+
el("path", { d: "M7 0l1.6 5.4L14 7l-5.4 1.6L7 14l-1.6-5.4L0 7l5.4-1.6z", fill: "currentColor" })
|
|
4776
|
+
);
|
|
4777
|
+
}
|
|
4778
|
+
function ChoiceView({ choice, picked, onPick }) {
|
|
4779
|
+
const [adding, setAdding] = d2(false);
|
|
4780
|
+
const [typed, setTyped] = d2("");
|
|
4781
|
+
const known = new Set(choice.options.map((option) => option.value));
|
|
4782
|
+
const own = picked.filter((value) => !known.has(value));
|
|
4783
|
+
const toggle = (value) => {
|
|
4784
|
+
if (!choice.multiple) return onPick([value]);
|
|
4785
|
+
onPick(picked.includes(value) ? picked.filter((v4) => v4 !== value) : [...picked, value]);
|
|
4786
|
+
};
|
|
4787
|
+
const add = () => {
|
|
4788
|
+
const value = typed.trim();
|
|
4789
|
+
if (value) onPick(choice.multiple ? [...picked.filter((v4) => v4 !== value), value] : [value]);
|
|
4790
|
+
setTyped("");
|
|
4791
|
+
setAdding(false);
|
|
4792
|
+
};
|
|
4793
|
+
const adder = choice.custom ? adding ? el(
|
|
4794
|
+
"div",
|
|
4795
|
+
{ class: "onboarding__add" },
|
|
4796
|
+
el("input", {
|
|
4797
|
+
type: "text",
|
|
4798
|
+
value: typed,
|
|
4799
|
+
placeholder: choice.custom,
|
|
4800
|
+
"aria-label": choice.custom,
|
|
4801
|
+
ref: (input) => input?.focus(),
|
|
4802
|
+
onInput: (event) => setTyped(event.currentTarget.value),
|
|
4803
|
+
onKeyDown: (event) => {
|
|
4804
|
+
if (event.key === "Enter") {
|
|
4805
|
+
event.preventDefault();
|
|
4806
|
+
add();
|
|
4807
|
+
}
|
|
4808
|
+
if (event.key === "Escape") setAdding(false);
|
|
4809
|
+
}
|
|
4810
|
+
}),
|
|
4811
|
+
el("button", { type: "button", class: "button", onClick: add }, "Add")
|
|
4812
|
+
) : el("button", { type: "button", class: "onboarding__add-button", onClick: () => setAdding(true) }, choice.multiple ? choice.custom : "Other\u2026") : null;
|
|
4813
|
+
const label = choice.label ? el("legend", { class: "onboarding__legend" }, choice.label) : null;
|
|
4814
|
+
const rows = choice.multiple || choice.options.some((option) => option.detail);
|
|
4815
|
+
if (!rows) {
|
|
4816
|
+
return el(
|
|
4817
|
+
"fieldset",
|
|
4818
|
+
{ class: "onboarding__choice" },
|
|
4819
|
+
label,
|
|
4820
|
+
el(
|
|
4821
|
+
"div",
|
|
4822
|
+
{ class: "onboarding__pills", role: "radiogroup" },
|
|
4823
|
+
[...choice.options, ...own.map((value) => ({ value, label: value, detail: null, group: null }))].map(
|
|
4824
|
+
(option) => el(
|
|
4825
|
+
"button",
|
|
4826
|
+
{
|
|
4827
|
+
type: "button",
|
|
4828
|
+
key: option.value,
|
|
4829
|
+
role: "radio",
|
|
4830
|
+
class: "onboarding__pill",
|
|
4831
|
+
"aria-checked": String(picked.includes(option.value)),
|
|
4832
|
+
onClick: () => toggle(option.value)
|
|
4833
|
+
},
|
|
4834
|
+
option.label
|
|
4835
|
+
)
|
|
4836
|
+
),
|
|
4837
|
+
adder
|
|
4838
|
+
)
|
|
4839
|
+
);
|
|
4840
|
+
}
|
|
4841
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4842
|
+
for (const option of choice.options) groups.set(option.group, [...groups.get(option.group) ?? [], option]);
|
|
4843
|
+
if (own.length) groups.set("Added by you", own.map((value) => ({ value, label: value, detail: null, group: "Added by you" })));
|
|
4844
|
+
return el(
|
|
4845
|
+
"fieldset",
|
|
4846
|
+
{ class: "onboarding__choice" },
|
|
4847
|
+
label,
|
|
4848
|
+
el(
|
|
4849
|
+
"div",
|
|
4850
|
+
{ class: "onboarding__list" },
|
|
4851
|
+
[...groups].map(
|
|
4852
|
+
([group, options]) => el(
|
|
4853
|
+
"div",
|
|
4854
|
+
{ class: "onboarding__group", key: group ?? "" },
|
|
4855
|
+
group ? el("p", { class: "onboarding__group-name" }, group) : null,
|
|
4856
|
+
options.map(
|
|
4857
|
+
(option) => el(
|
|
4858
|
+
"label",
|
|
4859
|
+
{ class: "onboarding__option", key: option.value },
|
|
4860
|
+
el("input", {
|
|
4861
|
+
type: choice.multiple ? "checkbox" : "radio",
|
|
4862
|
+
name: choice.id,
|
|
4863
|
+
checked: picked.includes(option.value),
|
|
4864
|
+
onChange: () => toggle(option.value)
|
|
4865
|
+
}),
|
|
4866
|
+
el("span", { class: "onboarding__box", "data-kind": choice.multiple ? "check" : "radio", "aria-hidden": "true" }),
|
|
4867
|
+
el("span", { class: "onboarding__option-text" }, option.label, option.detail ? el("small", null, option.detail) : null)
|
|
4868
|
+
)
|
|
4869
|
+
)
|
|
4870
|
+
)
|
|
4871
|
+
),
|
|
4872
|
+
choice.options.length === 0 && own.length === 0 && !choice.custom ? el("p", { class: "onboarding__none" }, "Nothing to choose from.") : null,
|
|
4873
|
+
adder ? el("div", { class: "onboarding__adder" }, adder) : null
|
|
4874
|
+
)
|
|
4875
|
+
);
|
|
4876
|
+
}
|
|
4877
|
+
function fieldView(field, value, onInput) {
|
|
4878
|
+
return el(
|
|
4879
|
+
"div",
|
|
4880
|
+
{ class: "onboarding__field", key: field.id },
|
|
4881
|
+
el(
|
|
4882
|
+
"label",
|
|
4883
|
+
null,
|
|
4884
|
+
el("span", { class: "onboarding__field-label" }, field.label),
|
|
4885
|
+
el("input", {
|
|
4886
|
+
type: "text",
|
|
4887
|
+
value,
|
|
4888
|
+
placeholder: field.placeholder ?? void 0,
|
|
4889
|
+
onInput: (event) => onInput(event.currentTarget.value)
|
|
4890
|
+
})
|
|
4891
|
+
),
|
|
4892
|
+
field.help ? el("small", null, field.help) : null,
|
|
4893
|
+
// Offered, never filled in: a click puts one in the field, to edit or keep.
|
|
4894
|
+
field.suggestions?.length ? el(
|
|
4895
|
+
"ul",
|
|
4896
|
+
{ class: "onboarding__suggestions" },
|
|
4897
|
+
field.suggestions.map(
|
|
4898
|
+
(suggestion) => el(
|
|
4899
|
+
"li",
|
|
4900
|
+
{ key: suggestion.text },
|
|
4901
|
+
el(
|
|
4902
|
+
"button",
|
|
4903
|
+
{ type: "button", "aria-pressed": String(value === suggestion.text), onClick: () => onInput(suggestion.text) },
|
|
4904
|
+
el("span", null, suggestion.text),
|
|
4905
|
+
suggestion.detail ? el("small", null, suggestion.detail) : null
|
|
4906
|
+
)
|
|
4907
|
+
)
|
|
4908
|
+
)
|
|
4909
|
+
) : null
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
4912
|
+
function FinishCard({ flow, ui: ui2, handlers: handlers2 }) {
|
|
4913
|
+
const writes = flow.steps.find((step) => step.id === "cli")?.status === "answered";
|
|
4914
|
+
const busy = ui2.setupBusy.value !== null;
|
|
4915
|
+
const error = (ui2.setupError.value?.step === "finish" ? ui2.setupError.value.message : null) ?? flow.error;
|
|
4916
|
+
const sources = ui2.setupSources.value;
|
|
4917
|
+
return el(
|
|
4918
|
+
"section",
|
|
4919
|
+
{ class: "onboarding__card onboarding__finish", "data-current": "true", "aria-labelledby": "setup-finish" },
|
|
4920
|
+
el("span", { class: "onboarding__dot onboarding__dot--open", "aria-hidden": "true" }),
|
|
4921
|
+
el("h2", { id: "setup-finish", tabIndex: -1 }, writes ? "Ready for your first brief" : "Ready to finish"),
|
|
4922
|
+
el(
|
|
4923
|
+
"p",
|
|
4924
|
+
{ class: "onboarding__why" },
|
|
4925
|
+
writes ? "It takes a few minutes, and you can watch it work. After that it runs by itself, and everything here can be changed in Settings." : "Without an agent there is no brief to write yet. Choose one in Settings when you are ready; the boards work meanwhile."
|
|
4926
|
+
),
|
|
4927
|
+
el(
|
|
4928
|
+
"details",
|
|
4929
|
+
{
|
|
4930
|
+
class: "onboarding__preview",
|
|
4931
|
+
onToggle: (event) => {
|
|
4932
|
+
if (event.currentTarget.open && sources === null) handlers2.loadSetupSources();
|
|
4933
|
+
}
|
|
4934
|
+
},
|
|
4935
|
+
el("summary", null, "What the morning agent reads each morning"),
|
|
4936
|
+
sources === null || sources === "loading" ? el("p", null, "Loading\u2026") : "error" in sources ? el("p", null, `Could not show it: ${sources.error}`) : sources.kept ? el("p", null, "Your source list is already written, and setup leaves it as it is.") : sources.text ? el("pre", null, sources.text) : el("p", null, "Nothing yet: the agent briefs from what it can reach, and says what it missed.")
|
|
4937
|
+
),
|
|
4938
|
+
error ? el("p", { class: "onboarding__error", role: "alert" }, error) : null,
|
|
4939
|
+
el(
|
|
4940
|
+
"div",
|
|
4941
|
+
{ class: "onboarding__actions" },
|
|
4942
|
+
el(
|
|
4943
|
+
"button",
|
|
4944
|
+
{ type: "button", class: "button button--primary", disabled: busy, onClick: () => handlers2.finishSetup() },
|
|
4945
|
+
ui2.setupBusy.value === "finish" ? "Saving\u2026" : writes ? "Write my first brief" : "Finish setup"
|
|
4946
|
+
)
|
|
4947
|
+
)
|
|
4948
|
+
);
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4302
4951
|
// client/settings.ts
|
|
4303
4952
|
var VIEW_NAME = {
|
|
4304
4953
|
today: "Today",
|
|
@@ -4313,6 +4962,11 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
4313
4962
|
}, []);
|
|
4314
4963
|
const page = ui2.settings.value;
|
|
4315
4964
|
const draft = ui2.settingsDraft.value;
|
|
4965
|
+
const sections = [
|
|
4966
|
+
{ id: "objective", title: "Your objective" },
|
|
4967
|
+
{ id: "sources", title: "Where the agent looks" },
|
|
4968
|
+
...page?.groups ?? []
|
|
4969
|
+
];
|
|
4316
4970
|
return el(
|
|
4317
4971
|
"div",
|
|
4318
4972
|
{ class: "settings" },
|
|
@@ -4322,6 +4976,7 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
4322
4976
|
el("h2", { class: "settings__title" }, "Settings"),
|
|
4323
4977
|
el("button", { type: "button", class: "button", onClick: () => handlers2.closeSettings() }, `Back to ${VIEW_NAME[ui2.settingsReturn.value]}`)
|
|
4324
4978
|
),
|
|
4979
|
+
k(SectionIndex, { sections }),
|
|
4325
4980
|
page?.error ? el("p", { class: "settings__problem", role: "alert" }, page.error) : null,
|
|
4326
4981
|
objectiveCard(state2, ui2, handlers2),
|
|
4327
4982
|
sourcesCard(state2, ui2, handlers2),
|
|
@@ -4346,6 +5001,29 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
4346
5001
|
saveBar(ui2, handlers2)
|
|
4347
5002
|
);
|
|
4348
5003
|
}
|
|
5004
|
+
function SectionIndex({ sections }) {
|
|
5005
|
+
const [current, setCurrent] = d2(sections[0]?.id ?? null);
|
|
5006
|
+
h2(() => {
|
|
5007
|
+
const look = () => {
|
|
5008
|
+
let seen = sections[0]?.id ?? null;
|
|
5009
|
+
for (const section2 of sections) {
|
|
5010
|
+
const card = document.getElementById(`settings-${section2.id}`);
|
|
5011
|
+
if (card && card.getBoundingClientRect().top <= 120) seen = section2.id;
|
|
5012
|
+
}
|
|
5013
|
+
setCurrent(seen);
|
|
5014
|
+
};
|
|
5015
|
+
look();
|
|
5016
|
+
window.addEventListener("scroll", look, { passive: true });
|
|
5017
|
+
return () => window.removeEventListener("scroll", look);
|
|
5018
|
+
}, [sections.map((section2) => section2.id).join(" ")]);
|
|
5019
|
+
return el(
|
|
5020
|
+
"nav",
|
|
5021
|
+
{ class: "settings__nav", "aria-label": "Settings sections" },
|
|
5022
|
+
sections.map(
|
|
5023
|
+
(section2) => el("a", { key: section2.id, href: `#settings-${section2.id}`, "aria-current": section2.id === current ? "true" : void 0 }, section2.title)
|
|
5024
|
+
)
|
|
5025
|
+
);
|
|
5026
|
+
}
|
|
4349
5027
|
function objectiveCard(state2, ui2, handlers2) {
|
|
4350
5028
|
const focus = state2.focus;
|
|
4351
5029
|
return el(
|
|
@@ -4373,7 +5051,7 @@ function objectiveCard(state2, ui2, handlers2) {
|
|
|
4373
5051
|
)
|
|
4374
5052
|
);
|
|
4375
5053
|
}
|
|
4376
|
-
function objectiveForm(objective, blocker, handlers2
|
|
5054
|
+
function objectiveForm(objective, blocker, handlers2) {
|
|
4377
5055
|
return el(
|
|
4378
5056
|
"form",
|
|
4379
5057
|
{
|
|
@@ -4404,7 +5082,7 @@ ${blocker}`,
|
|
|
4404
5082
|
el("span", { class: "field__label" }, "Blocked on (optional)"),
|
|
4405
5083
|
el("input", { class: "field__input", name: "blocker", defaultValue: blocker, placeholder: "What is in the way today, if anything", autocomplete: "off" })
|
|
4406
5084
|
),
|
|
4407
|
-
el("div", { class: "objective-form__actions" }, el("button", { type: "submit", class: "button button--primary" },
|
|
5085
|
+
el("div", { class: "objective-form__actions" }, el("button", { type: "submit", class: "button button--primary" }, "Save objective"))
|
|
4408
5086
|
);
|
|
4409
5087
|
}
|
|
4410
5088
|
function sourcesCard(state2, ui2, handlers2) {
|
|
@@ -4474,13 +5152,14 @@ function shownValue(setting, draft) {
|
|
|
4474
5152
|
if (draft.has(setting.key)) return draft.get(setting.key) ?? "";
|
|
4475
5153
|
return setting.source === "default" ? "" : setting.value ?? "";
|
|
4476
5154
|
}
|
|
5155
|
+
var SEGMENTS = 4;
|
|
4477
5156
|
function flagValue(raw) {
|
|
4478
5157
|
if (raw === "") return "";
|
|
4479
5158
|
return ["off", "false", "0", "no"].includes(raw.trim().toLowerCase()) ? "off" : "on";
|
|
4480
5159
|
}
|
|
4481
5160
|
function settingField(setting, draft, handlers2) {
|
|
4482
5161
|
const id = `setting-${setting.key}`;
|
|
4483
|
-
const locked = setting.source === "
|
|
5162
|
+
const locked = setting.source === "fixed";
|
|
4484
5163
|
const value = shownValue(setting, draft);
|
|
4485
5164
|
const onChange = (event) => {
|
|
4486
5165
|
const next = event.currentTarget.value;
|
|
@@ -4488,13 +5167,36 @@ function settingField(setting, draft, handlers2) {
|
|
|
4488
5167
|
};
|
|
4489
5168
|
let input;
|
|
4490
5169
|
if (setting.kind === "choice" || setting.kind === "flag") {
|
|
4491
|
-
const
|
|
5170
|
+
const choices = setting.kind === "flag" ? [{ value: "on", label: "On" }, { value: "off", label: "Off" }] : setting.choices ?? [];
|
|
4492
5171
|
const current = setting.kind === "flag" ? flagValue(value) : value;
|
|
4493
|
-
|
|
5172
|
+
const fallback = choices.find((choice) => choice.label.toLowerCase() === setting.fallback.toLowerCase());
|
|
5173
|
+
input = choices.length <= SEGMENTS && fallback ? el(
|
|
5174
|
+
"div",
|
|
5175
|
+
{ class: "segmented", id, role: "radiogroup", "aria-label": setting.label, "aria-disabled": String(locked) },
|
|
5176
|
+
choices.map((choice) => {
|
|
5177
|
+
const isDefault = choice.value === fallback.value;
|
|
5178
|
+
const checked = current === "" ? isDefault : current === choice.value;
|
|
5179
|
+
return el(
|
|
5180
|
+
"button",
|
|
5181
|
+
{
|
|
5182
|
+
type: "button",
|
|
5183
|
+
key: choice.value,
|
|
5184
|
+
role: "radio",
|
|
5185
|
+
class: "segmented__option",
|
|
5186
|
+
"aria-checked": String(checked),
|
|
5187
|
+
disabled: locked,
|
|
5188
|
+
title: isDefault ? "The default" : void 0,
|
|
5189
|
+
onClick: () => handlers2.editSetting(setting.key, isDefault ? null : choice.value)
|
|
5190
|
+
},
|
|
5191
|
+
choice.label,
|
|
5192
|
+
checked && current === "" ? el("small", null, "default") : null
|
|
5193
|
+
);
|
|
5194
|
+
})
|
|
5195
|
+
) : el(
|
|
4494
5196
|
"select",
|
|
4495
5197
|
{ class: "field__input", id, disabled: locked, value: current, onChange },
|
|
4496
5198
|
el("option", { value: "" }, `Default: ${setting.fallback}`),
|
|
4497
|
-
|
|
5199
|
+
choices.map((choice) => el("option", { value: choice.value, key: choice.value }, choice.label))
|
|
4498
5200
|
);
|
|
4499
5201
|
} else {
|
|
4500
5202
|
input = el("input", {
|
|
@@ -4510,7 +5212,7 @@ function settingField(setting, draft, handlers2) {
|
|
|
4510
5212
|
onInput: onChange
|
|
4511
5213
|
});
|
|
4512
5214
|
}
|
|
4513
|
-
const note =
|
|
5215
|
+
const note = locked ? "Fixed in the demo, so that nothing in it runs." : null;
|
|
4514
5216
|
return el(
|
|
4515
5217
|
"div",
|
|
4516
5218
|
{ class: "field", key: setting.key, "data-changed": String(draft.has(setting.key)) },
|
|
@@ -4533,9 +5235,9 @@ function calendarPicker(state2, ui2, handlers2, settings) {
|
|
|
4533
5235
|
);
|
|
4534
5236
|
}
|
|
4535
5237
|
const setting = settings.find((entry) => entry.key === "DAILY_FOCUS_CALENDARS");
|
|
4536
|
-
if (!setting || setting.source === "
|
|
5238
|
+
if (!setting || setting.source === "fixed") return null;
|
|
4537
5239
|
const chosen = shownValue(setting, ui2.settingsDraft.value).split(",").map((name) => name.trim()).filter(Boolean);
|
|
4538
|
-
const
|
|
5240
|
+
const listed2 = ui2.calendars.value;
|
|
4539
5241
|
const toggle = (title, on) => {
|
|
4540
5242
|
const others = chosen.filter((name) => name.toLowerCase() !== title.toLowerCase());
|
|
4541
5243
|
const next = on ? [...others, title] : others;
|
|
@@ -4545,11 +5247,11 @@ function calendarPicker(state2, ui2, handlers2, settings) {
|
|
|
4545
5247
|
"div",
|
|
4546
5248
|
{ class: "calendar-picker" },
|
|
4547
5249
|
el("button", { type: "button", class: "button", onClick: () => handlers2.listCalendars() }, "List the calendars on this Mac"),
|
|
4548
|
-
typeof
|
|
4549
|
-
Array.isArray(
|
|
5250
|
+
typeof listed2 === "string" ? el("p", { class: "settings-card__warning" }, listed2) : null,
|
|
5251
|
+
Array.isArray(listed2) ? el(
|
|
4550
5252
|
"ul",
|
|
4551
5253
|
{ class: "calendar-picker__list" },
|
|
4552
|
-
|
|
5254
|
+
listed2.map(
|
|
4553
5255
|
(entry) => el(
|
|
4554
5256
|
"li",
|
|
4555
5257
|
{ key: `${entry.title}
|
|
@@ -4592,249 +5294,61 @@ function saveBar(ui2, handlers2) {
|
|
|
4592
5294
|
}
|
|
4593
5295
|
|
|
4594
5296
|
// client/setup.ts
|
|
4595
|
-
|
|
4596
|
-
function SetupCard({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
4597
|
-
const setup = state2.setup;
|
|
5297
|
+
function FirstBriefCard({ state: state2, handlers: handlers2 }) {
|
|
4598
5298
|
const agent = state2.agentRun;
|
|
4599
|
-
const
|
|
4600
|
-
const
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
el(
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
),
|
|
4624
|
-
// A choice, so neither answer is drawn as the one to pick.
|
|
4625
|
-
choices([
|
|
4626
|
-
["Work", () => handlers2.chooseSettings({ DAILY_FOCUS_PROFILE: "work" })],
|
|
4627
|
-
["Personal life", () => handlers2.chooseSettings({ DAILY_FOCUS_PROFILE: "personal" })]
|
|
4628
|
-
])
|
|
4629
|
-
]
|
|
4630
|
-
),
|
|
4631
|
-
step(
|
|
4632
|
-
Boolean(state2.focus?.objective),
|
|
4633
|
-
"What are you trying to achieve?",
|
|
4634
|
-
state2.focus?.objective ? el("p", null, state2.focus.objective) : [
|
|
4635
|
-
el("p", null, "The agent ranks the day against it. Leave it for now if nothing stands out."),
|
|
4636
|
-
objectiveForm("", "", handlers2, "Set objective")
|
|
4637
|
-
]
|
|
4638
|
-
),
|
|
4639
|
-
step(
|
|
4640
|
-
agent.cli !== null,
|
|
4641
|
-
"Which agent writes the brief?",
|
|
4642
|
-
agent.cli ? [
|
|
4643
|
-
el(
|
|
4644
|
-
"p",
|
|
4645
|
-
null,
|
|
4646
|
-
`${CLI_LABEL[agent.cli]}. ` + (agent.schedule ? `After the first brief, it runs by itself each scheduled morning at ${agent.schedule.at}. ` : "It runs when you press refresh. ") + "It has to be logged in; the first run says if it is not."
|
|
4647
|
-
),
|
|
4648
|
-
agentAdvanced(agent.cli, ui2, handlers2)
|
|
4649
|
-
] : agentChoice(state2, handlers2)
|
|
4650
|
-
),
|
|
4651
|
-
step(setup.sources.exists && setup.sources.placeholders === 0, "Who are you, and where should it look?", sourcesStep(state2, ui2, handlers2)),
|
|
4652
|
-
step(null, "What else should the dashboard show?", boards(state2, handlers2)),
|
|
4653
|
-
step(
|
|
4654
|
-
null,
|
|
4655
|
-
"Your first brief",
|
|
4656
|
-
[
|
|
4657
|
-
el(
|
|
4658
|
-
"p",
|
|
4659
|
-
null,
|
|
4660
|
-
running ? "The morning agent is writing it now. Click the banner above to watch." : 'The morning agent writes it now, which takes a few minutes, and from then on by itself every scheduled morning. When it is done, click "updated \u2026 ago" at the top to read its report: what it found, and anything it could not reach.' + (setup.sources.exists ? "" : " Without a source list it briefs from what it can reach on its own, and says what it missed.")
|
|
4661
|
-
),
|
|
4662
|
-
placeholders ? el("p", { class: "setup__note setup__note--todo" }, "Replace the source list's placeholders first, or the agent goes looking for them.") : null,
|
|
4663
|
-
el(
|
|
4664
|
-
"button",
|
|
4665
|
-
{
|
|
4666
|
-
type: "button",
|
|
4667
|
-
class: "button button--primary",
|
|
4668
|
-
disabled: !agent.enabled || running || state2.restart === "waiting" || placeholders,
|
|
4669
|
-
title: !agent.enabled ? "Choose the agent first" : placeholders ? "Replace the source list's placeholders first" : void 0,
|
|
4670
|
-
onClick: () => handlers2.runAgent()
|
|
4671
|
-
},
|
|
4672
|
-
running ? "Running\u2026" : "Run the morning agent now"
|
|
4673
|
-
)
|
|
4674
|
-
]
|
|
4675
|
-
)
|
|
4676
|
-
)
|
|
4677
|
-
);
|
|
4678
|
-
}
|
|
4679
|
-
function step(done, title, body) {
|
|
4680
|
-
return el(
|
|
4681
|
-
"li",
|
|
4682
|
-
{ class: "setup__step", "data-done": done === null ? void 0 : String(done) },
|
|
4683
|
-
el(
|
|
4684
|
-
"span",
|
|
4685
|
-
{ class: "setup__mark", "aria-label": done === null ? void 0 : done ? "Done" : "To do" },
|
|
4686
|
-
done === null ? "\xB7" : done ? "\u2713" : "\u25CB"
|
|
4687
|
-
),
|
|
4688
|
-
el("div", { class: "setup__body" }, el("h3", { class: "setup__step-title" }, title), body)
|
|
4689
|
-
);
|
|
4690
|
-
}
|
|
4691
|
-
function choices(options) {
|
|
4692
|
-
return el(
|
|
4693
|
-
"div",
|
|
4694
|
-
{ class: "setup__choices" },
|
|
4695
|
-
options.map(([label, onClick, title]) => el("button", { type: "button", class: "button", key: label, title, onClick }, label))
|
|
4696
|
-
);
|
|
4697
|
-
}
|
|
4698
|
-
function agentChoice(state2, handlers2) {
|
|
4699
|
-
const found = ["claude", "codex"].filter((cli) => state2.setup.clis[cli]);
|
|
4700
|
-
if (found.length === 0) {
|
|
4701
|
-
return [
|
|
4702
|
-
el(
|
|
4703
|
-
"p",
|
|
4704
|
-
null,
|
|
4705
|
-
"The brief is written by a coding-agent CLI you already use. Install ",
|
|
4706
|
-
el("a", { href: "https://code.claude.com/docs", target: "_blank", rel: "noopener" }, "Claude Code"),
|
|
4707
|
-
" or the ",
|
|
4708
|
-
el("a", { href: "https://developers.openai.com/codex/cli", target: "_blank", rel: "noopener" }, "Codex CLI"),
|
|
4709
|
-
", log in, and restart the dashboard; it looks for both each time it starts."
|
|
4710
|
-
)
|
|
5299
|
+
const last = agent.last;
|
|
5300
|
+
const running = last?.status === "running";
|
|
5301
|
+
const restarting = state2.restart === "waiting";
|
|
5302
|
+
let title;
|
|
5303
|
+
let text;
|
|
5304
|
+
let actions;
|
|
5305
|
+
if (restarting) {
|
|
5306
|
+
title = "Saving your settings";
|
|
5307
|
+
text = "The dashboard restarts to apply them, then starts on your first brief.";
|
|
5308
|
+
actions = [];
|
|
5309
|
+
} else if (!agent.enabled) {
|
|
5310
|
+
title = "Choose who writes your brief";
|
|
5311
|
+
text = "Each morning a coding-agent CLI you already use writes it: Claude Code or Codex. The boards work meanwhile.";
|
|
5312
|
+
actions = [el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.openSettings("agent") }, "Choose in Settings")];
|
|
5313
|
+
} else if (running) {
|
|
5314
|
+
title = "Writing your first brief";
|
|
5315
|
+
text = "The morning agent is reading your mail, calendar and the rest. It takes a few minutes; the brief appears here when it is done.";
|
|
5316
|
+
actions = [el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Watch it work")];
|
|
5317
|
+
} else if (last && last.status !== "done") {
|
|
5318
|
+
title = "Your first brief didn\u2019t finish";
|
|
5319
|
+
text = last.status === "aborted" ? "It was stopped before it wrote a brief." : last.error ? `The run failed: ${last.error}.` : "The run stopped before it wrote a brief.";
|
|
5320
|
+
actions = [
|
|
5321
|
+
el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Try again"),
|
|
5322
|
+
el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Read its report")
|
|
4711
5323
|
];
|
|
5324
|
+
} else if (last) {
|
|
5325
|
+
title = "Your first brief didn\u2019t arrive";
|
|
5326
|
+
text = "The run finished without a brief this dashboard could read. Its report says why.";
|
|
5327
|
+
actions = [
|
|
5328
|
+
el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Run it again"),
|
|
5329
|
+
el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Read its report")
|
|
5330
|
+
];
|
|
5331
|
+
} else {
|
|
5332
|
+
title = "Write your first brief";
|
|
5333
|
+
text = "It takes a few minutes. After that it runs by itself each scheduled morning.";
|
|
5334
|
+
actions = [el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Write it now")];
|
|
4712
5335
|
}
|
|
4713
|
-
return [
|
|
4714
|
-
el("p", null, "The dashboard runs a coding-agent CLI you already use, headless, each morning. Found on this machine:"),
|
|
4715
|
-
choices(
|
|
4716
|
-
found.map(
|
|
4717
|
-
(cli) => [`Use ${CLI_LABEL[cli]}`, () => handlers2.chooseSettings({ DAILY_FOCUS_AGENT: cli }), state2.setup.clis[cli] ?? void 0]
|
|
4718
|
-
)
|
|
4719
|
-
)
|
|
4720
|
-
];
|
|
4721
|
-
}
|
|
4722
|
-
var AGENT_ADVANCED = "setup:agent-advanced";
|
|
4723
|
-
function agentAdvanced(cli, ui2, handlers2) {
|
|
4724
|
-
const keys = ["DAILY_FOCUS_AGENT_MODEL", "DAILY_FOCUS_AGENT_EFFORT", ...cli === "claude" ? ["DAILY_FOCUS_AGENT_TOOLS"] : []];
|
|
4725
|
-
const page = ui2.settings.value;
|
|
4726
|
-
const draft = ui2.settingsDraft.value;
|
|
4727
|
-
const changed = keys.some((key) => draft.has(key));
|
|
4728
|
-
const error = changed ? ui2.settingsError.value : null;
|
|
4729
|
-
return el(
|
|
4730
|
-
"details",
|
|
4731
|
-
{
|
|
4732
|
-
class: "setup__advanced",
|
|
4733
|
-
open: ui2.openDrawers.value.has(AGENT_ADVANCED),
|
|
4734
|
-
onToggle: (event) => {
|
|
4735
|
-
const open = event.currentTarget.open;
|
|
4736
|
-
if (open && !ui2.settings.peek()) handlers2.loadSettings();
|
|
4737
|
-
handlers2.toggleDrawer(AGENT_ADVANCED, open);
|
|
4738
|
-
}
|
|
4739
|
-
},
|
|
4740
|
-
el("summary", null, cli === "claude" ? "Advanced: model, effort and tools" : "Advanced: model and effort"),
|
|
4741
|
-
page ? [
|
|
4742
|
-
...keys.flatMap((key) => {
|
|
4743
|
-
const setting = page.settings.find((entry) => entry.key === key);
|
|
4744
|
-
return setting ? [settingField(setting, draft, handlers2)] : [];
|
|
4745
|
-
}),
|
|
4746
|
-
error ? el("p", { class: "setup__note setup__note--todo", role: "alert" }, error) : null,
|
|
4747
|
-
el(
|
|
4748
|
-
"div",
|
|
4749
|
-
{ class: "setup__advanced-actions" },
|
|
4750
|
-
el(
|
|
4751
|
-
"button",
|
|
4752
|
-
{ type: "button", class: "button", disabled: !changed || ui2.settingsSaving.value, onClick: () => handlers2.saveSettings(keys) },
|
|
4753
|
-
ui2.settingsSaving.value ? "Saving\u2026" : "Save"
|
|
4754
|
-
)
|
|
4755
|
-
)
|
|
4756
|
-
] : el("p", null, "Loading the settings\u2026")
|
|
4757
|
-
);
|
|
4758
|
-
}
|
|
4759
|
-
var SOURCES_EDITOR = "setup:sources";
|
|
4760
|
-
function sourcesStep(state2, ui2, handlers2) {
|
|
4761
|
-
const sources = state2.setup.sources;
|
|
4762
|
-
const open = ui2.openDrawers.value.has(SOURCES_EDITOR);
|
|
4763
|
-
const toggle = () => {
|
|
4764
|
-
if (!open && !ui2.texts.value.sources) handlers2.loadText("sources");
|
|
4765
|
-
handlers2.toggleDrawer(SOURCES_EDITOR, !open);
|
|
4766
|
-
};
|
|
4767
|
-
return [
|
|
4768
|
-
el(
|
|
4769
|
-
"p",
|
|
4770
|
-
null,
|
|
4771
|
-
"A short list the agent reads every morning. First who you are: your name as meeting notes write it, your email and your GitHub login, so it can tell your to-dos from everyone else's. Then where to look: which calendars, mailboxes and documents are yours."
|
|
4772
|
-
),
|
|
4773
|
-
sources.exists ? el(
|
|
4774
|
-
"p",
|
|
4775
|
-
{ class: sources.placeholders > 0 ? "setup__note setup__note--todo" : "setup__note" },
|
|
4776
|
-
sources.placeholders > 0 ? `It still has ${sources.placeholders} of the template's placeholders. Replace them, or delete what doesn't apply.` : "Written."
|
|
4777
|
-
) : el("p", { class: "setup__note" }, "It starts from a template, with what gh and git know about you already filled in."),
|
|
4778
|
-
el(
|
|
4779
|
-
"button",
|
|
4780
|
-
{ type: "button", class: "button", "aria-expanded": String(open), onClick: toggle },
|
|
4781
|
-
open ? "Close" : sources.exists ? "Edit it" : "Write it"
|
|
4782
|
-
),
|
|
4783
|
-
open ? el("div", { class: "setup__editor" }, textEditor("sources", ui2.texts.value.sources, handlers2, "sources.md")) : null
|
|
4784
|
-
];
|
|
4785
|
-
}
|
|
4786
|
-
var HEALTH_MARK = { ok: "\u2713", checking: "\u2026", problem: "!", todo: "\u25CB" };
|
|
4787
|
-
var HEALTH_LABEL = {
|
|
4788
|
-
ok: "Connected",
|
|
4789
|
-
checking: "Checking",
|
|
4790
|
-
problem: "Not working",
|
|
4791
|
-
todo: "To do"
|
|
4792
|
-
};
|
|
4793
|
-
function boardHealth(board, via, service) {
|
|
4794
|
-
if (board.reason) return ["problem", board.reason.message];
|
|
4795
|
-
if (board.fetchedAt) return ["ok", `Connected, through ${via}.`];
|
|
4796
|
-
return ["checking", `Checking ${service} through ${via}\u2026`];
|
|
4797
|
-
}
|
|
4798
|
-
function boards(state2, handlers2) {
|
|
4799
|
-
const rows = [];
|
|
4800
|
-
const off = (key, label) => el("button", { type: "button", class: "button", onClick: () => handlers2.chooseSettings({ [key]: "off" }) }, `Turn off ${label}`);
|
|
4801
|
-
if (state2.board.enabled) {
|
|
4802
|
-
rows.push(boardRow("Pull requests", ...boardHealth(state2.board, "gh", "GitHub"), off("DAILY_FOCUS_GITHUB", "the pull request board")));
|
|
4803
|
-
}
|
|
4804
|
-
if (state2.tickets.enabled) {
|
|
4805
|
-
rows.push(boardRow("Jira tickets", ...boardHealth(state2.tickets, "acli", "Jira"), off("DAILY_FOCUS_JIRA", "the Jira board")));
|
|
4806
|
-
}
|
|
4807
|
-
const calendar = state2.setup.calendar;
|
|
4808
|
-
if (calendar.supported && state2.agendaSource) {
|
|
4809
|
-
const live = state2.agendaSource.live;
|
|
4810
|
-
const [health, text] = !calendar.built ? ["todo", `Needs the calendar helper: run ${calendar.buildCommand} once. Until then the agenda uses the brief.`] : calendar.chosen === 0 ? ["todo", "No calendars chosen yet. Choose them, and the agenda follows them live."] : live ? ["ok", `Connected, reading ${calendar.chosen} ${calendar.chosen === 1 ? "calendar" : "calendars"} from Calendar.app.`] : state2.agendaSource.problem ? ["problem", state2.agendaSource.problem] : ["checking", "Checking Calendar.app\u2026"];
|
|
4811
|
-
rows.push(
|
|
4812
|
-
boardRow(
|
|
4813
|
-
"Calendar",
|
|
4814
|
-
health,
|
|
4815
|
-
text,
|
|
4816
|
-
calendar.built && calendar.chosen === 0 ? el("button", { type: "button", class: "button", onClick: () => handlers2.openSettings("calendar") }, "Choose calendars") : off("DAILY_FOCUS_CALENDAR", "the live agenda")
|
|
4817
|
-
)
|
|
4818
|
-
);
|
|
4819
|
-
}
|
|
4820
|
-
if (rows.length === 0) return el("p", null, "Nothing else is switched on. The boards can be turned on in Settings.");
|
|
4821
|
-
return el("ul", { class: "setup__boards" }, rows);
|
|
4822
|
-
}
|
|
4823
|
-
function boardRow(name, health, text, action) {
|
|
4824
5336
|
return el(
|
|
4825
|
-
"
|
|
4826
|
-
{ class: "
|
|
4827
|
-
el("
|
|
4828
|
-
el("
|
|
4829
|
-
el("
|
|
4830
|
-
action
|
|
5337
|
+
"section",
|
|
5338
|
+
{ class: "first-brief", id: "first-brief", "aria-labelledby": "first-brief-title", "data-state": running ? "running" : "idle" },
|
|
5339
|
+
el("h2", { class: "first-brief__title", id: "first-brief-title" }, title),
|
|
5340
|
+
el("p", { class: "first-brief__text" }, text),
|
|
5341
|
+
actions.length ? el("div", { class: "first-brief__actions" }, actions) : null
|
|
4831
5342
|
);
|
|
4832
5343
|
}
|
|
4833
5344
|
|
|
4834
5345
|
// client/today.ts
|
|
4835
5346
|
function TodayView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
4836
|
-
if (state2.setup?.needed) return [k(
|
|
4837
|
-
return [
|
|
5347
|
+
if (state2.setup?.needed) return [k(FirstBriefCard, { state: state2, handlers: handlers2 })];
|
|
5348
|
+
return [
|
|
5349
|
+
el("div", { class: "today__masthead" }, renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2)),
|
|
5350
|
+
renderSections(state2, ui2, handlers2)
|
|
5351
|
+
];
|
|
4838
5352
|
}
|
|
4839
5353
|
function renderObjective(state2, handlers2 = null) {
|
|
4840
5354
|
const focus = state2.focus;
|
|
@@ -4890,15 +5404,15 @@ function focusTimeStat(agenda, now) {
|
|
|
4890
5404
|
const tone = minutes === 0 ? "critical" : minutes < 60 ? "critical" : null;
|
|
4891
5405
|
return stat("Focus time left", minutes === 0 ? "None" : formatDuration(minutes), tone, `Day ends ${agenda.dayEnd}`);
|
|
4892
5406
|
}
|
|
4893
|
-
function objectiveStat(
|
|
4894
|
-
if (!
|
|
4895
|
-
if (
|
|
5407
|
+
function objectiveStat(progress2) {
|
|
5408
|
+
if (!progress2) return null;
|
|
5409
|
+
if (progress2.workingDaysSince === null) {
|
|
4896
5410
|
return stat("Since objective progress", "\u2014", null, "Nothing recorded yet");
|
|
4897
5411
|
}
|
|
4898
|
-
const days =
|
|
5412
|
+
const days = progress2.workingDaysSince;
|
|
4899
5413
|
const tone = days === 0 ? "good" : days >= 3 ? "critical" : null;
|
|
4900
5414
|
const value = days === 0 ? "Today" : days === 1 ? "1 day" : `${days} days`;
|
|
4901
|
-
return stat("Since objective progress", value, tone,
|
|
5415
|
+
return stat("Since objective progress", value, tone, progress2.lastTitle);
|
|
4902
5416
|
}
|
|
4903
5417
|
function renderHeadline(state2) {
|
|
4904
5418
|
const headline = state2.brief.headline;
|
|
@@ -4953,10 +5467,12 @@ function App({ ui: ui2, handlers: handlers2 }) {
|
|
|
4953
5467
|
const view = ui2.view.value;
|
|
4954
5468
|
const run = current ? detailRun(current, ui2) : null;
|
|
4955
5469
|
const row = current && !run ? detailRow(current, ui2) : null;
|
|
5470
|
+
if (current?.onboarding) return [k(OnboardingView, { state: current, ui: ui2, handlers: handlers2 }), k(Toast, { ui: ui2, handlers: handlers2 })];
|
|
4956
5471
|
return [
|
|
4957
5472
|
el("a", { class: "skip-link", href: "#main" }, "Skip to content"),
|
|
4958
|
-
|
|
4959
|
-
|
|
5473
|
+
// The band: the masthead's navy runs behind both, and on Today on behind
|
|
5474
|
+
// the objective below (see `.band` in the stylesheet).
|
|
5475
|
+
el("div", { class: "band" }, k(Header, { state: current, ui: ui2, handlers: handlers2 }), k(Tabs, { state: current, ui: ui2, handlers: handlers2 })),
|
|
4960
5476
|
el("div", { id: "banners", class: "banners" }, current ? renderBanners(current, ui2, handlers2) : null),
|
|
4961
5477
|
el(
|
|
4962
5478
|
"main",
|
|
@@ -5116,12 +5632,12 @@ function installKeyboard(handlers2) {
|
|
|
5116
5632
|
}
|
|
5117
5633
|
const item = selectedItem();
|
|
5118
5634
|
if (!item) return;
|
|
5119
|
-
const
|
|
5635
|
+
const boardRow = ui.view.value !== "today";
|
|
5120
5636
|
const status = "status" in item ? item.status : null;
|
|
5121
5637
|
switch (event.key) {
|
|
5122
5638
|
case "e":
|
|
5123
5639
|
event.preventDefault();
|
|
5124
|
-
if (!
|
|
5640
|
+
if (!boardRow && status === "open") handlers2.onAction(item.id, "done");
|
|
5125
5641
|
break;
|
|
5126
5642
|
case "s":
|
|
5127
5643
|
event.preventDefault();
|
|
@@ -5129,7 +5645,7 @@ function installKeyboard(handlers2) {
|
|
|
5129
5645
|
break;
|
|
5130
5646
|
case "x":
|
|
5131
5647
|
event.preventDefault();
|
|
5132
|
-
if (!
|
|
5648
|
+
if (!boardRow && status === "open") handlers2.onAction(item.id, "dismiss");
|
|
5133
5649
|
break;
|
|
5134
5650
|
case "n":
|
|
5135
5651
|
event.preventDefault();
|
|
@@ -5159,6 +5675,9 @@ R(k(App, { ui, handlers }), root);
|
|
|
5159
5675
|
j3(() => {
|
|
5160
5676
|
document.body.dataset.view = ui.view.value;
|
|
5161
5677
|
});
|
|
5678
|
+
j3(() => {
|
|
5679
|
+
document.body.dataset.onboarding = String(Boolean(state.value?.onboarding));
|
|
5680
|
+
});
|
|
5162
5681
|
j3(() => {
|
|
5163
5682
|
document.body.dataset.offline = String(ui.connectionError.value !== null);
|
|
5164
5683
|
});
|