daily-focus 2.0.0-dev.15 → 2.0.0-dev.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daily-focus",
3
- "version": "2.0.0-dev.15",
3
+ "version": "2.0.0-dev.16",
4
4
  "description": "An interactive dashboard of what you should focus on today at work",
5
5
  "keywords": [
6
6
  "dashboard",
@@ -340,7 +340,7 @@ Then the general editorial bar:
340
340
  {
341
341
  "version": 1,
342
342
  "generatedAt": "<ISO 8601 timestamp, with offset>",
343
- "generatedBy": "<name yourself — your model or agent id>",
343
+ "generatedBy": "<your model or agent id, such as codex, and nothing else>",
344
344
  "date": "<today, YYYY-MM-DD>",
345
345
  "headline": "<one or two sentences, or omit>",
346
346
  "items": [ … ]
@@ -418,7 +418,9 @@ Check all of these, and fix anything that fails:
418
418
  6. Every `url` starts with `http://` or `https://`, and came from a tool rather than
419
419
  being built by you.
420
420
  7. Every `start`/`end` has a timezone offset, unless deliberately all-day.
421
- 8. `generatedAt` is now, and `generatedBy` names whoever ran.
421
+ 8. `generatedAt` is now, and `generatedBy` names whoever ran — your model or agent
422
+ id and nothing else: not a working directory, a path or an account. The dashboard
423
+ prints it after "updated … by" in its header.
422
424
  9. **When `focus.md` sets an objective, at least one item offers a concrete path to
423
425
  it** — a real one you found, or the synthesised one. With no objective, no item has
424
426
  `advancesObjective`.
@@ -512,7 +512,7 @@ Then the general editorial bar:
512
512
  {
513
513
  "version": 1,
514
514
  "generatedAt": "<ISO 8601 timestamp, with offset>",
515
- "generatedBy": "<name yourself — your model or agent id>",
515
+ "generatedBy": "<your model or agent id, such as codex, and nothing else>",
516
516
  "date": "<today, YYYY-MM-DD>",
517
517
  "headline": "<one or two sentences, or omit>",
518
518
  "dayStart": "09:00",
@@ -601,7 +601,9 @@ Check all of these, and fix anything that fails:
601
601
  6. Every `url` starts with `http://` or `https://`.
602
602
  7. Every `start`/`end` has a timezone offset, unless deliberately all-day.
603
603
  8. `generatedAt` is now, and `generatedBy` names whoever ran — your model or agent
604
- id, so the dashboard can show who produced the brief.
604
+ id and nothing else: not a working directory, a path or an account. The dashboard
605
+ prints it after "updated … by" in its header, so `codex` reads well there and
606
+ `codex (/root)` does not.
605
607
  9. **When `focus.md` sets an objective, at least one item offers a concrete path to
606
608
  it** — a real one you found, or the synthesised "no path today" item. A brief with
607
609
  no route to the objective and no acknowledgement of that fact is a failed run.
package/public/app.js CHANGED
@@ -2480,10 +2480,11 @@ function agentRunBanner(run, state2, handlers2) {
2480
2480
  };
2481
2481
  if (run.status === "running") {
2482
2482
  const helping = (run.steps ?? []).filter((step) => step.subagent && step.state === "running").length;
2483
+ const doing = briefLanded(run, state2.brief?.generatedAt) ? "The brief is in. The morning agent is checking it and writing its report" : "The morning agent is writing a new brief";
2483
2484
  return bannerNode(
2484
2485
  "info",
2485
2486
  "i",
2486
- `The morning agent is writing a new brief, started ${relativeTime(run.startedAt, now)}` + (helping ? `, with ${helping} ${helping === 1 ? "subagent" : "subagents"} at work.` : "."),
2487
+ `${doing}, started ${relativeTime(run.startedAt, now)}` + (helping ? `, with ${helping} ${helping === 1 ? "subagent" : "subagents"} at work.` : "."),
2487
2488
  [el("button", { type: "button", class: "button banner__action", onClick: () => handlers2.stopAgent() }, "Stop")],
2488
2489
  strip
2489
2490
  );
@@ -2504,6 +2505,11 @@ function agentRunBanner(run, state2, handlers2) {
2504
2505
  strip
2505
2506
  );
2506
2507
  }
2508
+ function briefLanded(run, generatedAt) {
2509
+ const started = parseDate(run.startedAt)?.getTime();
2510
+ const landed = generatedAt ? parseDate(generatedAt)?.getTime() : void 0;
2511
+ return started !== void 0 && landed !== void 0 && landed >= started;
2512
+ }
2507
2513
  function isHeldOverDayOff(state2) {
2508
2514
  if (state2.brief.generatedAt === null || state2.schedule.runsToday) return false;
2509
2515
  const today = dayKey(new Date(state2.now));
@@ -3731,7 +3737,7 @@ function briefRefresh(state2, handlers2) {
3731
3737
  const agent = state2.agentRun;
3732
3738
  if (!agent?.enabled) return null;
3733
3739
  const running = agent.last?.status === "running";
3734
- const label = running ? `The morning agent is writing a new brief, started ${formatTime(agent.last?.startedAt)}` : `Run the morning agent now, for a fresh brief${nextRunPhraseFor(agent)}`;
3740
+ const label = running ? agent.last && briefLanded(agent.last, state2.brief.generatedAt) ? `The brief is in; the morning agent is checking it and writing its report, started ${formatTime(agent.last.startedAt)}` : `The morning agent is writing a new brief, started ${formatTime(agent.last?.startedAt)}` : `Run the morning agent now, for a fresh brief${nextRunPhraseFor(agent)}`;
3735
3741
  return el(
3736
3742
  "button",
3737
3743
  {
@@ -3898,8 +3904,8 @@ function RunPanel({ run, state: state2, ui: ui2, handlers: handlers2 }) {
3898
3904
  el(
3899
3905
  "div",
3900
3906
  { class: "flyout__body", ref: body, onScroll },
3901
- runStatus(run, now, handlers2),
3902
- runReport(run, ui2, handlers2),
3907
+ run.status === "running" ? null : runStatus(run, now, handlers2),
3908
+ runReport(run, { landed: briefLanded(run, state2.brief?.generatedAt), now }, ui2, handlers2),
3903
3909
  run.status === "running" ? null : runConversation(run, busy, answering, enabled, ui2, handlers2, askInput),
3904
3910
  runList(run, runs, now, agent, handlers2)
3905
3911
  )
@@ -3936,8 +3942,8 @@ function describeWhen(value, now) {
3936
3942
  const named = day === "today" || day === "tomorrow" || day === "yesterday";
3937
3943
  return `${named ? day : date && date.getFullYear() === now.getFullYear() ? formatShortDay(value) : formatDay(value)} at ${formatTime(value)}`;
3938
3944
  }
3939
- function runReport(run, ui2, handlers2) {
3940
- if (run.steps?.length) return runWork(run, ui2, handlers2);
3945
+ function runReport(run, live, ui2, handlers2) {
3946
+ if (run.steps?.length) return runWork(run, live, ui2, handlers2);
3941
3947
  const messages = run.messages ?? (run.report ? [run.report] : []);
3942
3948
  const running = run.status === "running";
3943
3949
  const earlier = !running && messages.length && messages.at(-1) === run.report ? messages.slice(0, -1) : messages;
@@ -3976,12 +3982,9 @@ function runReport(run, ui2, handlers2) {
3976
3982
  })
3977
3983
  ) : null;
3978
3984
  if (running) {
3979
- return el(
3980
- "section",
3981
- { class: "flyout__section" },
3982
- el("h3", { class: "flyout__label" }, "What it is doing"),
3983
- list ?? el("p", { class: "flyout__empty" }, "Nothing yet.")
3984
- );
3985
+ const latest = earlier.at(-1);
3986
+ const summary = `${latest ? firstLine(latest) : "Starting"} \xB7 ${earlier.length} ${earlier.length === 1 ? "message" : "messages"}`;
3987
+ return liveSection(run, live, summary, [list, el("ol", { class: "assistant__steps agent-run__steps" }, waitingLine(earlier.length > 0))], ui2, handlers2);
3985
3988
  }
3986
3989
  return el(
3987
3990
  "section",
@@ -3996,16 +3999,9 @@ function runReport(run, ui2, handlers2) {
3996
3999
  ) : null
3997
4000
  );
3998
4001
  }
3999
- function runWork(run, ui2, handlers2) {
4002
+ function runWork(run, live, ui2, handlers2) {
4000
4003
  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
- }
4004
+ if (running) return liveSection(run, live, workSummary(run), runSteps(run, run.steps, null, true, ui2, handlers2), ui2, handlers2);
4009
4005
  const last = run.steps.findLast((step) => step.by === null && step.kind === "message");
4010
4006
  const steps2 = last && sameWords(last.text, run.report) ? run.steps.filter((step) => step !== last) : run.steps;
4011
4007
  const handoffs = steps2.filter((step) => step.subagent).length;
@@ -4022,29 +4018,122 @@ function runWork(run, ui2, handlers2) {
4022
4018
  null,
4023
4019
  `How it got there, in ${steps2.length} ${steps2.length === 1 ? "step" : "steps"}` + (handoffs ? `, with ${handoffs} ${handoffs === 1 ? "subagent" : "subagents"}` : "")
4024
4020
  ),
4025
- runSteps(run, steps2, null, ui2, handlers2)
4021
+ runSteps(run, steps2, null, false, ui2, handlers2)
4026
4022
  ) : null
4027
4023
  );
4028
4024
  }
4025
+ function liveSection(run, live, summary, content, ui2, handlers2) {
4026
+ const phase = live.landed ? "The brief is in; checking it and writing its report" : "Writing a new brief";
4027
+ return el(
4028
+ "section",
4029
+ { class: "flyout__section" },
4030
+ el("h3", { class: "flyout__label" }, `${phase}, started ${relativeTime(run.startedAt, live.now)}`),
4031
+ el(
4032
+ "div",
4033
+ { class: "agent-run__live" },
4034
+ workFold(run, summary, content, ui2, handlers2),
4035
+ el("button", { type: "button", class: "button", onClick: () => handlers2.stopAgent() }, "Stop")
4036
+ )
4037
+ );
4038
+ }
4039
+ function workFold(run, summary, content, ui2, handlers2) {
4040
+ const key = `${run.id}:work`;
4041
+ return el(
4042
+ "details",
4043
+ {
4044
+ class: "agent-run__list agent-run__list--working",
4045
+ open: ui2.messageOpen.value.get(key) ?? false,
4046
+ onToggle: (event) => {
4047
+ const isOpen = event.currentTarget.open;
4048
+ handlers2.toggleMessage(key, isOpen ? true : null);
4049
+ }
4050
+ },
4051
+ el("summary", null, summary),
4052
+ content
4053
+ );
4054
+ }
4055
+ function workSummary(run) {
4056
+ const own = run.steps.filter((step) => step.by === null);
4057
+ const inFlight = own.findLast((step) => step.kind === "tool" && step.state === "running");
4058
+ const latest = own.at(-1);
4059
+ const working = run.steps.filter((step) => step.subagent && step.state === "running");
4060
+ const count = `${run.steps.length} ${run.steps.length === 1 ? "step" : "steps"}`;
4061
+ if (inFlight?.subagent) {
4062
+ return `Waiting on ${working.map((step) => step.detail ?? "a subagent").join(", ") || "a subagent"} \xB7 ${count}`;
4063
+ }
4064
+ const doing = inFlight ? inFlight.text : latest ? latest.kind === "message" ? firstLine(latest.text) : latest.text : "Starting";
4065
+ const helping = working.length;
4066
+ return `${doing} \xB7 ${count}${helping ? `, ${helping} ${helping === 1 ? "subagent" : "subagents"} at work` : ""}`;
4067
+ }
4068
+ function waitingLine(begun) {
4069
+ return el(
4070
+ "li",
4071
+ { class: "assistant__step assistant__step--waiting", "data-state": "running" },
4072
+ el("span", { class: "assistant__step-text" }, begun ? "Thinking\u2026" : "Starting\u2026")
4073
+ );
4074
+ }
4029
4075
  var NESTING = 4;
4030
- function runSteps(run, steps2, by, ui2, handlers2, depth = 0) {
4031
- const own = steps2.filter((step) => step.by === by);
4076
+ var CALLS_FOLD = 2;
4077
+ function runSteps(run, steps2, by, active, ui2, handlers2, depth = 0) {
4078
+ const own = steps2.map((step, index) => ({ step, index })).filter(({ step }) => step.by === by);
4079
+ const inFlight = own.some(({ step }) => step.state === "running");
4080
+ const items = [];
4081
+ let calls = [];
4082
+ const flush = () => {
4083
+ if (calls.length >= CALLS_FOLD) items.push(callsFold(run, by, calls, ui2, handlers2));
4084
+ else for (const placed of calls) items.push(callLine(placed));
4085
+ calls = [];
4086
+ };
4087
+ for (const placed of own) {
4088
+ const { step, index } = placed;
4089
+ const folds = step.subagent && depth < NESTING;
4090
+ if (step.kind === "tool" && !folds && step.state !== "running") {
4091
+ calls.push(placed);
4092
+ continue;
4093
+ }
4094
+ flush();
4095
+ if (step.kind === "message") items.push(el("li", { key: index, class: "agent-run__said" }, renderMarkdownBlocks(step.text)));
4096
+ else if (folds) items.push(runHandoff(run, steps2, step, ui2, handlers2, depth, index));
4097
+ else items.push(callLine(placed));
4098
+ }
4099
+ flush();
4032
4100
  return el(
4033
4101
  "ol",
4034
4102
  { 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
- })
4103
+ own.length || active ? null : el("li", { class: "assistant__step assistant__step--message" }, "Nothing yet."),
4104
+ items,
4105
+ active && !inFlight ? waitingLine(own.length > 0) : null
4106
+ );
4107
+ }
4108
+ function callLine({ step, index }) {
4109
+ return el(
4110
+ "li",
4111
+ { key: index, class: "assistant__step", "data-state": step.state },
4112
+ el("span", { class: "assistant__step-text" }, step.text),
4113
+ step.detail ? el("code", { class: "assistant__step-detail", title: step.detail }, step.detail) : null
4114
+ );
4115
+ }
4116
+ function callsFold(run, by, calls, ui2, handlers2) {
4117
+ const key = `${run.id}:${by ?? "run"}:calls@${calls[0].index}`;
4118
+ const failed = calls.filter(({ step }) => step.state === "failed").length;
4119
+ const names = [...new Set(calls.map(({ step }) => step.text))];
4120
+ const named = names.length <= 2 ? names.join(", ") : `${names.slice(0, 2).join(", ")} and ${names.length - 2} more`;
4121
+ const summary = `${calls.length} calls${failed ? `, ${failed} failed` : ""} \xB7 ${named}`;
4122
+ return el(
4123
+ "li",
4124
+ { key, class: "assistant__step agent-run__calls", "data-state": failed ? "failed" : "done" },
4125
+ el(
4126
+ "details",
4127
+ {
4128
+ open: ui2.messageOpen.value.get(key) ?? false,
4129
+ onToggle: (event) => {
4130
+ const isOpen = event.currentTarget.open;
4131
+ handlers2.toggleMessage(key, isOpen ? true : null);
4132
+ }
4133
+ },
4134
+ el("summary", null, summary),
4135
+ el("ol", { class: "assistant__steps agent-run__steps" }, calls.map((placed) => callLine(placed)))
4136
+ )
4048
4137
  );
4049
4138
  }
4050
4139
  function runHandoff(run, steps2, step, ui2, handlers2, depth, index) {
@@ -4054,8 +4143,7 @@ function runHandoff(run, steps2, step, ui2, handlers2, depth, index) {
4054
4143
  const calls = own.filter((candidate) => candidate.kind === "tool").length;
4055
4144
  const doing = step.state === "running" ? latest ? latest.kind === "message" ? firstLine(latest.text) : latest.text : "starting" : `${calls} ${calls === 1 ? "step" : "steps"}`;
4056
4145
  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;
4146
+ const open = ui2.messageOpen.value.get(key) ?? false;
4059
4147
  return el(
4060
4148
  "li",
4061
4149
  { key: index, class: "assistant__step agent-run__handoff", "data-state": step.state },
@@ -4066,7 +4154,7 @@ function runHandoff(run, steps2, step, ui2, handlers2, depth, index) {
4066
4154
  // As the message folds do: a toggle back to the default is forgotten.
4067
4155
  onToggle: (event) => {
4068
4156
  const isOpen = event.currentTarget.open;
4069
- handlers2.toggleMessage(key, isOpen === fallback ? null : isOpen);
4157
+ handlers2.toggleMessage(key, isOpen ? true : null);
4070
4158
  }
4071
4159
  },
4072
4160
  el(
@@ -4075,7 +4163,7 @@ function runHandoff(run, steps2, step, ui2, handlers2, depth, index) {
4075
4163
  el("span", { class: "assistant__step-text" }, `Subagent: ${step.detail ?? "unnamed"}`),
4076
4164
  el("span", { class: "assistant__step-detail" }, doing)
4077
4165
  ),
4078
- runSteps(run, steps2, id, ui2, handlers2, depth + 1)
4166
+ runSteps(run, steps2, id, step.state === "running", ui2, handlers2, depth + 1)
4079
4167
  )
4080
4168
  );
4081
4169
  }
@@ -4083,14 +4171,6 @@ function sameWords(a4, b4) {
4083
4171
  return a4.replace(/\s+/g, " ").trim() === b4.replace(/\s+/g, " ").trim();
4084
4172
  }
4085
4173
  function runStatus(run, now, handlers2) {
4086
- if (run.status === "running") {
4087
- return el(
4088
- "p",
4089
- { class: "agent-run__status" },
4090
- `Writing a new brief, started ${relativeTime(run.startedAt, now)}. `,
4091
- el("button", { type: "button", class: "button", onClick: () => handlers2.stopAgent() }, "Stop")
4092
- );
4093
- }
4094
4174
  const took = runDuration(run);
4095
4175
  if (run.status === "done") {
4096
4176
  return el("p", { class: "agent-run__status" }, `Finished at ${formatTime(run.endedAt)}${took ? `, in ${took}` : ""}.`);
package/public/style.css CHANGED
@@ -679,6 +679,24 @@ body[data-view='tickets'] #brief-refresh {
679
679
  .agent-run__list summary:hover {
680
680
  color: var(--ink-1);
681
681
  }
682
+ /* The one line of a run still going: what it is on now, so it reads a step darker than a finished run's fold. */
683
+ .agent-run__list--working > summary {
684
+ color: var(--ink-2);
685
+ white-space: normal;
686
+ }
687
+ /* That line and Stop on one row; opened, the steps grow under the line and Stop stays put at the top. */
688
+ .agent-run__live {
689
+ display: flex;
690
+ align-items: flex-start;
691
+ gap: 10px;
692
+ }
693
+ .agent-run__live > details {
694
+ flex: 1 1 auto;
695
+ min-width: 0;
696
+ }
697
+ .agent-run__live > .button {
698
+ flex: none;
699
+ }
682
700
  /* What the agent said along the way, one fold per message, under its first line. */
683
701
  .agent-run__messages {
684
702
  display: flex;
@@ -2347,8 +2365,12 @@ a.button {
2347
2365
  font-variant-numeric: tabular-nums;
2348
2366
  }
2349
2367
 
2350
- /* A spinner, for the turn while it works and for each call it is waiting on. */
2368
+ /*
2369
+ * A spinner, for the turn while it works and for each call it is waiting on;
2370
+ * and on the line a running run folds under, which is all of it that shows.
2371
+ */
2351
2372
  .assistant__status--working::after,
2373
+ .agent-run__list--working > summary::after,
2352
2374
  .assistant__step[data-state='running']::before {
2353
2375
  content: '';
2354
2376
  display: inline-block;
@@ -2360,7 +2382,8 @@ a.button {
2360
2382
  border-top-color: transparent;
2361
2383
  animation: assistant-spin 0.9s linear infinite;
2362
2384
  }
2363
- .assistant__status--working::after {
2385
+ .assistant__status--working::after,
2386
+ .agent-run__list--working > summary::after {
2364
2387
  margin-left: 6px;
2365
2388
  vertical-align: -1px;
2366
2389
  }
@@ -2371,6 +2394,7 @@ a.button {
2371
2394
  }
2372
2395
  @media (prefers-reduced-motion: reduce) {
2373
2396
  .assistant__status--working::after,
2397
+ .agent-run__list--working > summary::after,
2374
2398
  .assistant__step[data-state='running']::before {
2375
2399
  animation: none;
2376
2400
  }
@@ -2447,6 +2471,31 @@ a.button {
2447
2471
  font-size: 11.5px;
2448
2472
  color: var(--ink-3);
2449
2473
  }
2474
+ /* A run of calls between two things the agent said, folded to the one line that counts them. */
2475
+ .agent-run__calls > details {
2476
+ flex: 1 1 auto;
2477
+ min-width: 0;
2478
+ }
2479
+ .agent-run__calls summary {
2480
+ cursor: pointer;
2481
+ overflow: hidden;
2482
+ text-overflow: ellipsis;
2483
+ white-space: nowrap;
2484
+ color: var(--ink-3);
2485
+ }
2486
+ .agent-run__calls summary:hover {
2487
+ color: var(--ink-1);
2488
+ }
2489
+ .agent-run__calls .agent-run__steps {
2490
+ margin-top: 4px;
2491
+ }
2492
+
2493
+ /* Between calls: composing, which no call records. Quieter than a call, since nothing has happened yet. */
2494
+ .assistant__step--waiting .assistant__step-text {
2495
+ font-style: italic;
2496
+ color: var(--ink-3);
2497
+ }
2498
+
2450
2499
  /* What it said on the way: its own words, in the reading face, set apart from the calls by having no mark. */
2451
2500
  .assistant__step--message {
2452
2501
  display: block;