daily-focus 1.2.0-dev.7 → 1.2.0-dev.8
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 +0 -9
- package/SETUP.md +3 -5
- package/dist/{audit-7YAJYHZF.js → audit-DNTFZOHH.js} +3 -3
- package/dist/{chunk-4DCBCR4D.js → chunk-6SMUSPZG.js} +2 -2
- package/dist/{chunk-JQGYCWRX.js → chunk-GQ3NCWRT.js} +7 -175
- package/dist/{chunk-RNE27FQY.js → chunk-KZNY45XW.js} +0 -5
- package/dist/{chunk-UY6HQLGE.js → chunk-P3E2B2SD.js} +1 -1
- package/dist/cli.js +6 -6
- package/dist/{demo-5I4BNHW2.js → demo-6Q22AAC7.js} +2 -21
- package/dist/{init-57WHZZNG.js → init-XDMAXU2S.js} +1 -9
- package/dist/{seed-BLFBCHIA.js → seed-TWT7EDXJ.js} +3 -3
- package/dist/{server-UI6WYRTL.js → server-PIYOIUKJ.js} +87 -217
- package/dist/{service-NAAAV67B.js → service-ZUHI3VKH.js} +1 -1
- package/package.json +1 -1
- package/prompts/README.md +2 -3
- package/prompts/morning-brief-personal.md +0 -25
- package/prompts/morning-brief-work.md +0 -28
- package/public/app.js +134 -334
- package/public/style.css +5 -169
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
TimeZoneKeeper,
|
|
13
13
|
validZone
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-6SMUSPZG.js";
|
|
15
15
|
import {
|
|
16
16
|
describeLink,
|
|
17
17
|
linkStore,
|
|
@@ -22,12 +22,9 @@ import {
|
|
|
22
22
|
Store,
|
|
23
23
|
canonicalId,
|
|
24
24
|
foldActionLog,
|
|
25
|
-
reconcileSession,
|
|
26
|
-
startSession,
|
|
27
|
-
stopSession,
|
|
28
25
|
tempPathFor,
|
|
29
26
|
writeJsonAtomic
|
|
30
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-GQ3NCWRT.js";
|
|
31
28
|
import {
|
|
32
29
|
CLI_NAMES,
|
|
33
30
|
LAUNCH_SETTINGS,
|
|
@@ -43,7 +40,7 @@ import {
|
|
|
43
40
|
readDotEnv,
|
|
44
41
|
resolveSchedule,
|
|
45
42
|
scheduledRunDue
|
|
46
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-KZNY45XW.js";
|
|
47
44
|
import {
|
|
48
45
|
PACKAGED,
|
|
49
46
|
command,
|
|
@@ -500,9 +497,9 @@ function checkSlot(node) {
|
|
|
500
497
|
return `check\0${node.name ?? ""}`;
|
|
501
498
|
}
|
|
502
499
|
function checkGeneration(node) {
|
|
503
|
-
const
|
|
504
|
-
const sequence =
|
|
505
|
-
if (typeof sequence === "number") return [sequence,
|
|
500
|
+
const run5 = node.checkSuite?.workflowRun;
|
|
501
|
+
const sequence = run5?.runNumber ?? run5?.databaseId;
|
|
502
|
+
if (typeof sequence === "number") return [sequence, run5?.runAttempt ?? 0];
|
|
506
503
|
const at = node.completedAt ?? node.startedAt ?? node.createdAt ?? null;
|
|
507
504
|
const time = at ? Date.parse(at) : Number.NaN;
|
|
508
505
|
return [Number.isFinite(time) ? time : 0, 0];
|
|
@@ -2674,11 +2671,11 @@ function foldAgentLog(text) {
|
|
|
2674
2671
|
});
|
|
2675
2672
|
continue;
|
|
2676
2673
|
}
|
|
2677
|
-
const
|
|
2678
|
-
if (!
|
|
2674
|
+
const run5 = runs.get(record.run);
|
|
2675
|
+
if (!run5) continue;
|
|
2679
2676
|
if (record.event === "asked") {
|
|
2680
2677
|
if (typeof record.turn !== "string" || typeof record.question !== "string") continue;
|
|
2681
|
-
|
|
2678
|
+
run5.turns.push({
|
|
2682
2679
|
id: record.turn,
|
|
2683
2680
|
question: record.question,
|
|
2684
2681
|
startedAt: record.at,
|
|
@@ -2690,7 +2687,7 @@ function foldAgentLog(text) {
|
|
|
2690
2687
|
continue;
|
|
2691
2688
|
}
|
|
2692
2689
|
if (record.event === "replied" || record.event === "turn-failed" || record.event === "turn-aborted") {
|
|
2693
|
-
const turn =
|
|
2690
|
+
const turn = run5.turns.find((candidate) => candidate.id === record.turn);
|
|
2694
2691
|
if (!turn) continue;
|
|
2695
2692
|
turn.reply = typeof record.reply === "string" ? record.reply : turn.reply;
|
|
2696
2693
|
turn.endedAt = record.at;
|
|
@@ -2705,18 +2702,18 @@ function foldAgentLog(text) {
|
|
|
2705
2702
|
}
|
|
2706
2703
|
continue;
|
|
2707
2704
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2705
|
+
run5.sessionId = typeof record.sessionId === "string" ? record.sessionId : run5.sessionId;
|
|
2706
|
+
run5.report = typeof record.report === "string" ? record.report : run5.report;
|
|
2707
|
+
run5.messages = Array.isArray(record.messages) ? record.messages.filter((message) => typeof message === "string") : run5.report ? [run5.report] : [];
|
|
2708
|
+
run5.endedAt = record.at;
|
|
2712
2709
|
if (record.event === "finished") {
|
|
2713
|
-
|
|
2710
|
+
run5.status = "done";
|
|
2714
2711
|
} else if (record.event === "failed") {
|
|
2715
|
-
|
|
2716
|
-
|
|
2712
|
+
run5.status = "failed";
|
|
2713
|
+
run5.error = record.error;
|
|
2717
2714
|
} else if (record.event === "aborted") {
|
|
2718
|
-
|
|
2719
|
-
|
|
2715
|
+
run5.status = "aborted";
|
|
2716
|
+
run5.error = record.reason;
|
|
2720
2717
|
}
|
|
2721
2718
|
}
|
|
2722
2719
|
return [...runs.values()].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
@@ -2756,19 +2753,19 @@ var AgentRunner = class {
|
|
|
2756
2753
|
this.runs = foldAgentLog(text);
|
|
2757
2754
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2758
2755
|
const reason = "the dashboard restarted while this was running";
|
|
2759
|
-
for (const
|
|
2760
|
-
if (
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
await this.append({ event: "aborted", run:
|
|
2756
|
+
for (const run5 of this.runs) {
|
|
2757
|
+
if (run5.status === "running") {
|
|
2758
|
+
run5.status = "aborted";
|
|
2759
|
+
run5.error = reason;
|
|
2760
|
+
run5.endedAt = at;
|
|
2761
|
+
await this.append({ event: "aborted", run: run5.id, sessionId: run5.sessionId, reason, report: run5.report, messages: run5.messages, at });
|
|
2765
2762
|
}
|
|
2766
|
-
for (const turn of
|
|
2763
|
+
for (const turn of run5.turns) {
|
|
2767
2764
|
if (turn.status !== "running") continue;
|
|
2768
2765
|
turn.status = "aborted";
|
|
2769
2766
|
turn.error = reason;
|
|
2770
2767
|
turn.endedAt = at;
|
|
2771
|
-
await this.append({ event: "turn-aborted", run:
|
|
2768
|
+
await this.append({ event: "turn-aborted", run: run5.id, turn: turn.id, reason, reply: turn.reply, at });
|
|
2772
2769
|
}
|
|
2773
2770
|
}
|
|
2774
2771
|
}
|
|
@@ -2781,7 +2778,7 @@ var AgentRunner = class {
|
|
|
2781
2778
|
return this.runs.at(-1) ?? null;
|
|
2782
2779
|
}
|
|
2783
2780
|
view(now = /* @__PURE__ */ new Date()) {
|
|
2784
|
-
const runs = this.runs.slice(-RUNS_SHOWN).reverse().map((
|
|
2781
|
+
const runs = this.runs.slice(-RUNS_SHOWN).reverse().map((run5) => ({ ...run5, resumable: this.resumable(run5) }));
|
|
2785
2782
|
return {
|
|
2786
2783
|
enabled: this.enabled,
|
|
2787
2784
|
cli: this.config.agent.cli,
|
|
@@ -2835,7 +2832,7 @@ var AgentRunner = class {
|
|
|
2835
2832
|
);
|
|
2836
2833
|
}
|
|
2837
2834
|
await this.start();
|
|
2838
|
-
const
|
|
2835
|
+
const run5 = {
|
|
2839
2836
|
id: randomUUID(),
|
|
2840
2837
|
cli,
|
|
2841
2838
|
trigger,
|
|
@@ -2849,8 +2846,8 @@ var AgentRunner = class {
|
|
|
2849
2846
|
turns: [],
|
|
2850
2847
|
resumable: false
|
|
2851
2848
|
};
|
|
2852
|
-
this.runs.push(
|
|
2853
|
-
await this.append({ event: "started", run:
|
|
2849
|
+
this.runs.push(run5);
|
|
2850
|
+
await this.append({ event: "started", run: run5.id, cli, trigger, at: run5.startedAt });
|
|
2854
2851
|
const adapter = ADAPTERS[cli];
|
|
2855
2852
|
const args = adapter.args({
|
|
2856
2853
|
prompt: agentPrompt(displayPath(this.config.promptFile)),
|
|
@@ -2862,8 +2859,8 @@ var AgentRunner = class {
|
|
|
2862
2859
|
drafts: false,
|
|
2863
2860
|
codexConfig: []
|
|
2864
2861
|
});
|
|
2865
|
-
await this.spawnInto(
|
|
2866
|
-
return
|
|
2862
|
+
await this.spawnInto(run5, null, args, RUN_TIMEOUT_MS, "gave up after an hour");
|
|
2863
|
+
return run5;
|
|
2867
2864
|
}
|
|
2868
2865
|
/**
|
|
2869
2866
|
* Ask a finished run a question, in its own session. Returns once the process
|
|
@@ -2873,16 +2870,16 @@ var AgentRunner = class {
|
|
|
2873
2870
|
const cli = this.config.agent.cli;
|
|
2874
2871
|
if (!cli) throw new Error("running the morning agent is off; set DAILY_FOCUS_AGENT to codex or claude");
|
|
2875
2872
|
await this.start();
|
|
2876
|
-
const
|
|
2877
|
-
if (!
|
|
2873
|
+
const run5 = this.runs.find((candidate) => candidate.id === runId);
|
|
2874
|
+
if (!run5) throw new Error("no such run");
|
|
2878
2875
|
if (this.running) {
|
|
2879
2876
|
throw new Error(
|
|
2880
2877
|
this.running.turn ? "the morning agent is still answering a question" : "the morning agent is still running"
|
|
2881
2878
|
);
|
|
2882
2879
|
}
|
|
2883
|
-
if (!this.resumable(
|
|
2880
|
+
if (!this.resumable(run5)) {
|
|
2884
2881
|
throw new Error(
|
|
2885
|
-
|
|
2882
|
+
run5.sessionId === null ? "this run left no session to continue" : `this run was made by ${run5.cli}, and the agent is ${cli} now`
|
|
2886
2883
|
);
|
|
2887
2884
|
}
|
|
2888
2885
|
const text = question.trim();
|
|
@@ -2896,12 +2893,12 @@ var AgentRunner = class {
|
|
|
2896
2893
|
reply: "",
|
|
2897
2894
|
error: null
|
|
2898
2895
|
};
|
|
2899
|
-
|
|
2900
|
-
await this.append({ event: "asked", run:
|
|
2896
|
+
run5.turns.push(turn);
|
|
2897
|
+
await this.append({ event: "asked", run: run5.id, turn: turn.id, question: text, at: turn.startedAt });
|
|
2901
2898
|
const adapter = ADAPTERS[cli];
|
|
2902
2899
|
const args = adapter.args({
|
|
2903
2900
|
prompt: followUpPrompt(text),
|
|
2904
|
-
sessionId:
|
|
2901
|
+
sessionId: run5.sessionId,
|
|
2905
2902
|
model: this.config.agent.model,
|
|
2906
2903
|
effort: this.config.agent.effort,
|
|
2907
2904
|
// The sources, and not the brief: a chat is given nothing to write.
|
|
@@ -2910,7 +2907,7 @@ var AgentRunner = class {
|
|
|
2910
2907
|
drafts: false,
|
|
2911
2908
|
codexConfig: []
|
|
2912
2909
|
});
|
|
2913
|
-
await this.spawnInto(
|
|
2910
|
+
await this.spawnInto(run5, turn, args, TURN_TIMEOUT_MS, "gave up after ten minutes");
|
|
2914
2911
|
return turn;
|
|
2915
2912
|
}
|
|
2916
2913
|
/**
|
|
@@ -2918,13 +2915,13 @@ var AgentRunner = class {
|
|
|
2918
2915
|
* run when there is none. The store in both cases: Claude Code files a
|
|
2919
2916
|
* session under the directory it was started in, and looks for it there.
|
|
2920
2917
|
*/
|
|
2921
|
-
async spawnInto(
|
|
2922
|
-
const cli =
|
|
2918
|
+
async spawnInto(run5, turn, args, timeoutMs, timeoutReason) {
|
|
2919
|
+
const cli = run5.cli;
|
|
2923
2920
|
const adapter = ADAPTERS[cli];
|
|
2924
2921
|
await mkdir(this.config.dataDir, { recursive: true });
|
|
2925
2922
|
const refusal2 = cli === "codex" ? await this.codexGate.refusal() : null;
|
|
2926
2923
|
if (refusal2) {
|
|
2927
|
-
await this.finish(
|
|
2924
|
+
await this.finish(run5, turn, { ok: false, error: refusal2 });
|
|
2928
2925
|
return;
|
|
2929
2926
|
}
|
|
2930
2927
|
let child;
|
|
@@ -2936,12 +2933,12 @@ var AgentRunner = class {
|
|
|
2936
2933
|
env: process.env
|
|
2937
2934
|
});
|
|
2938
2935
|
} catch (err) {
|
|
2939
|
-
await this.finish(
|
|
2936
|
+
await this.finish(run5, turn, { ok: false, error: this.spawnError(err) });
|
|
2940
2937
|
return;
|
|
2941
2938
|
}
|
|
2942
2939
|
const timer = setTimeout(() => void this.stop(timeoutReason), timeoutMs);
|
|
2943
2940
|
timer.unref();
|
|
2944
|
-
const running = { run:
|
|
2941
|
+
const running = { run: run5, turn, child, timer, abortReason: null };
|
|
2945
2942
|
this.running = running;
|
|
2946
2943
|
this.hooks.onChange();
|
|
2947
2944
|
let done;
|
|
@@ -2955,15 +2952,15 @@ var AgentRunner = class {
|
|
|
2955
2952
|
lines.on("line", (line) => {
|
|
2956
2953
|
const event = adapter.parse(line);
|
|
2957
2954
|
if (!event) return;
|
|
2958
|
-
if (event.sessionId && !turn)
|
|
2955
|
+
if (event.sessionId && !turn) run5.sessionId = event.sessionId;
|
|
2959
2956
|
if (event.text) {
|
|
2960
2957
|
if (turn) {
|
|
2961
2958
|
turn.reply = turn.reply ? `${turn.reply}
|
|
2962
2959
|
|
|
2963
2960
|
${event.text}` : event.text;
|
|
2964
2961
|
} else {
|
|
2965
|
-
|
|
2966
|
-
|
|
2962
|
+
run5.messages.push(event.text);
|
|
2963
|
+
run5.report = event.text;
|
|
2967
2964
|
}
|
|
2968
2965
|
this.hooks.onChange();
|
|
2969
2966
|
}
|
|
@@ -2980,7 +2977,7 @@ ${event.text}` : event.text;
|
|
|
2980
2977
|
ok: false,
|
|
2981
2978
|
error: `${cli} exited with code ${code}${stderr.length ? `: ${stderr.join("").trim().split("\n").slice(-3).join(" ")}` : ""}`
|
|
2982
2979
|
};
|
|
2983
|
-
void this.finish(
|
|
2980
|
+
void this.finish(run5, turn, outcome, running.abortReason !== null);
|
|
2984
2981
|
});
|
|
2985
2982
|
}
|
|
2986
2983
|
/**
|
|
@@ -2997,45 +2994,45 @@ ${event.text}` : event.text;
|
|
|
2997
2994
|
async close() {
|
|
2998
2995
|
await this.stop("the dashboard shut down");
|
|
2999
2996
|
}
|
|
3000
|
-
resumable(
|
|
3001
|
-
return
|
|
2997
|
+
resumable(run5) {
|
|
2998
|
+
return run5.status !== "running" && run5.sessionId !== null && run5.cli === this.config.agent.cli;
|
|
3002
2999
|
}
|
|
3003
|
-
async finish(
|
|
3000
|
+
async finish(run5, turn, outcome, aborted = false) {
|
|
3004
3001
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3005
3002
|
if (turn) {
|
|
3006
3003
|
const reply = outcome.reply ?? turn.reply;
|
|
3007
3004
|
turn.reply = reply;
|
|
3008
3005
|
turn.endedAt = at;
|
|
3009
3006
|
if (outcome.ok) {
|
|
3010
|
-
await this.append({ event: "replied", run:
|
|
3007
|
+
await this.append({ event: "replied", run: run5.id, turn: turn.id, reply, at });
|
|
3011
3008
|
turn.status = "done";
|
|
3012
3009
|
} else if (aborted) {
|
|
3013
3010
|
turn.error = outcome.error ?? "stopped";
|
|
3014
|
-
await this.append({ event: "turn-aborted", run:
|
|
3011
|
+
await this.append({ event: "turn-aborted", run: run5.id, turn: turn.id, reason: turn.error, reply, at });
|
|
3015
3012
|
turn.status = "aborted";
|
|
3016
3013
|
} else {
|
|
3017
3014
|
turn.error = outcome.error ?? "failed";
|
|
3018
|
-
await this.append({ event: "turn-failed", run:
|
|
3015
|
+
await this.append({ event: "turn-failed", run: run5.id, turn: turn.id, error: turn.error, reply, at });
|
|
3019
3016
|
turn.status = "failed";
|
|
3020
3017
|
}
|
|
3021
3018
|
this.hooks.onChange();
|
|
3022
3019
|
return;
|
|
3023
3020
|
}
|
|
3024
|
-
const report = outcome.reply ??
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
const messages =
|
|
3021
|
+
const report = outcome.reply ?? run5.report;
|
|
3022
|
+
run5.report = report;
|
|
3023
|
+
run5.endedAt = at;
|
|
3024
|
+
const messages = run5.messages;
|
|
3028
3025
|
if (outcome.ok) {
|
|
3029
|
-
await this.append({ event: "finished", run:
|
|
3030
|
-
|
|
3026
|
+
await this.append({ event: "finished", run: run5.id, sessionId: run5.sessionId, report, messages, at });
|
|
3027
|
+
run5.status = "done";
|
|
3031
3028
|
} else if (aborted) {
|
|
3032
|
-
|
|
3033
|
-
await this.append({ event: "aborted", run:
|
|
3034
|
-
|
|
3029
|
+
run5.error = outcome.error ?? "stopped";
|
|
3030
|
+
await this.append({ event: "aborted", run: run5.id, sessionId: run5.sessionId, reason: run5.error, report, messages, at });
|
|
3031
|
+
run5.status = "aborted";
|
|
3035
3032
|
} else {
|
|
3036
|
-
|
|
3037
|
-
await this.append({ event: "failed", run:
|
|
3038
|
-
|
|
3033
|
+
run5.error = outcome.error ?? "failed";
|
|
3034
|
+
await this.append({ event: "failed", run: run5.id, sessionId: run5.sessionId, error: run5.error, report, messages, at });
|
|
3035
|
+
run5.status = "failed";
|
|
3039
3036
|
}
|
|
3040
3037
|
this.hooks.onChange();
|
|
3041
3038
|
}
|
|
@@ -3149,33 +3146,6 @@ function refusal(req, listenHost) {
|
|
|
3149
3146
|
return null;
|
|
3150
3147
|
}
|
|
3151
3148
|
|
|
3152
|
-
// src/presence.ts
|
|
3153
|
-
import { execFile as execFile4 } from "node:child_process";
|
|
3154
|
-
import { platform } from "node:os";
|
|
3155
|
-
import { promisify as promisify4 } from "node:util";
|
|
3156
|
-
var run4 = promisify4(execFile4);
|
|
3157
|
-
var PROBE_TIMEOUT_MS = 4e3;
|
|
3158
|
-
var HID_IDLE = /"HIDIdleTime"\s*=\s*(\d+)/;
|
|
3159
|
-
var readIdleSeconds = async () => {
|
|
3160
|
-
if (platform() !== "darwin") return null;
|
|
3161
|
-
try {
|
|
3162
|
-
const { stdout } = await run4("ioreg", ["-c", "IOHIDSystem", "-d", "1", "-r"], {
|
|
3163
|
-
timeout: PROBE_TIMEOUT_MS,
|
|
3164
|
-
maxBuffer: 1024 * 1024
|
|
3165
|
-
});
|
|
3166
|
-
return parseIdleSeconds(stdout);
|
|
3167
|
-
} catch {
|
|
3168
|
-
return null;
|
|
3169
|
-
}
|
|
3170
|
-
};
|
|
3171
|
-
function parseIdleSeconds(ioregOutput) {
|
|
3172
|
-
const match = HID_IDLE.exec(ioregOutput);
|
|
3173
|
-
if (!match) return null;
|
|
3174
|
-
const nanoseconds = Number(match[1]);
|
|
3175
|
-
if (!Number.isFinite(nanoseconds) || nanoseconds < 0) return null;
|
|
3176
|
-
return nanoseconds / 1e9;
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
3149
|
// src/settings.ts
|
|
3180
3150
|
var SETTING_GROUPS = [
|
|
3181
3151
|
{ id: "instance", title: "This dashboard" },
|
|
@@ -3184,8 +3154,7 @@ var SETTING_GROUPS = [
|
|
|
3184
3154
|
{ id: "github", title: "Pull requests" },
|
|
3185
3155
|
{ id: "jira", title: "Jira tickets" },
|
|
3186
3156
|
{ id: "calendar", title: "Calendar" },
|
|
3187
|
-
{ id: "day", title: "Working day" }
|
|
3188
|
-
{ id: "sessions", title: "Focus sessions" }
|
|
3157
|
+
{ id: "day", title: "Working day" }
|
|
3189
3158
|
];
|
|
3190
3159
|
var OFF_OR_CLI = [
|
|
3191
3160
|
{ value: "off", label: "Off" },
|
|
@@ -3199,7 +3168,7 @@ var SETTINGS = [
|
|
|
3199
3168
|
key: "DAILY_FOCUS_PROFILE",
|
|
3200
3169
|
group: "instance",
|
|
3201
3170
|
label: "What this dashboard briefs",
|
|
3202
|
-
help: "Work or home. Picks the morning prompt and the defaults below: the personal profile switches off Jira
|
|
3171
|
+
help: "Work or home. Picks the morning prompt and the defaults below: the personal profile switches off Jira and free time.",
|
|
3203
3172
|
kind: "choice",
|
|
3204
3173
|
choices: PROFILES.map((value) => ({ value, label: value === "work" ? "Work" : "Personal life" })),
|
|
3205
3174
|
fallback: always("Work")
|
|
@@ -3508,22 +3477,6 @@ var SETTINGS = [
|
|
|
3508
3477
|
help: "Minutes a gap has to last to count as time for focused work.",
|
|
3509
3478
|
kind: "number",
|
|
3510
3479
|
fallback: always("45 minutes")
|
|
3511
|
-
},
|
|
3512
|
-
{
|
|
3513
|
-
key: "DAILY_FOCUS_SESSION_MINUTES",
|
|
3514
|
-
group: "sessions",
|
|
3515
|
-
label: "Session length",
|
|
3516
|
-
help: "Minutes a focus session is set for when you start one.",
|
|
3517
|
-
kind: "number",
|
|
3518
|
-
fallback: always("25 minutes")
|
|
3519
|
-
},
|
|
3520
|
-
{
|
|
3521
|
-
key: "DAILY_FOCUS_AWAY_AFTER",
|
|
3522
|
-
group: "sessions",
|
|
3523
|
-
label: "End a session when away for",
|
|
3524
|
-
help: "Minutes without touching the Mac before a session is closed where you left. 0 turns it off.",
|
|
3525
|
-
kind: "number",
|
|
3526
|
-
fallback: (profile) => profile === "personal" ? "0, off" : "10 minutes"
|
|
3527
3480
|
}
|
|
3528
3481
|
];
|
|
3529
3482
|
var BY_KEY = new Map(SETTINGS.map((def) => [def.key, def]));
|
|
@@ -3586,11 +3539,11 @@ async function saveSettings(path, settings) {
|
|
|
3586
3539
|
}
|
|
3587
3540
|
|
|
3588
3541
|
// src/setup.ts
|
|
3589
|
-
import { execFile as
|
|
3542
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
3590
3543
|
import { access, constants, readdir as readdir2, readFile as readFile7, stat as stat3 } from "node:fs/promises";
|
|
3591
3544
|
import { delimiter, isAbsolute, join as join3 } from "node:path";
|
|
3592
|
-
import { promisify as
|
|
3593
|
-
var
|
|
3545
|
+
import { promisify as promisify4 } from "node:util";
|
|
3546
|
+
var run4 = promisify4(execFile4);
|
|
3594
3547
|
var PLACEHOLDERS = [
|
|
3595
3548
|
"Your Name",
|
|
3596
3549
|
"you@example.com",
|
|
@@ -3680,11 +3633,11 @@ var identity = null;
|
|
|
3680
3633
|
function detectIdentity(config) {
|
|
3681
3634
|
identity ??= (async () => {
|
|
3682
3635
|
const [user, email] = await Promise.all([
|
|
3683
|
-
|
|
3636
|
+
run4(config.github.ghPath, ["api", "user", "--jq", '[.login, .name // ""] | join("\\n")'], { timeout: 5e3 }).then(
|
|
3684
3637
|
({ stdout }) => stdout.split("\n"),
|
|
3685
3638
|
() => []
|
|
3686
3639
|
),
|
|
3687
|
-
|
|
3640
|
+
run4("git", ["config", "--global", "user.email"], { timeout: 2e3 }).then(
|
|
3688
3641
|
({ stdout }) => stdout.trim() || null,
|
|
3689
3642
|
() => null
|
|
3690
3643
|
)
|
|
@@ -3733,8 +3686,8 @@ var defaultExec = (file, args, { timeout }) => new Promise((resolve2, reject) =>
|
|
|
3733
3686
|
child.on("error", (error) => finish(error));
|
|
3734
3687
|
child.on("close", () => finish(null));
|
|
3735
3688
|
});
|
|
3736
|
-
function shellCandidates(envShell, loginShell,
|
|
3737
|
-
const fallback =
|
|
3689
|
+
function shellCandidates(envShell, loginShell, platform) {
|
|
3690
|
+
const fallback = platform === "darwin" ? "/bin/zsh" : platform === "linux" ? "/bin/bash" : void 0;
|
|
3738
3691
|
const shells = [];
|
|
3739
3692
|
for (const shell of [envShell, loginShell, fallback]) {
|
|
3740
3693
|
const trimmed = shell?.trim();
|
|
@@ -3779,11 +3732,11 @@ function currentLoginShell() {
|
|
|
3779
3732
|
return void 0;
|
|
3780
3733
|
}
|
|
3781
3734
|
}
|
|
3782
|
-
async function hydratePath(env = process.env,
|
|
3783
|
-
if (
|
|
3735
|
+
async function hydratePath(env = process.env, platform = process.platform, exec = defaultExec, loginShell = currentLoginShell()) {
|
|
3736
|
+
if (platform === "win32") return null;
|
|
3784
3737
|
let found = null;
|
|
3785
3738
|
let from = null;
|
|
3786
|
-
for (const shell of shellCandidates(env.SHELL, loginShell,
|
|
3739
|
+
for (const shell of shellCandidates(env.SHELL, loginShell, platform)) {
|
|
3787
3740
|
try {
|
|
3788
3741
|
found = await readLoginShellPath(shell, exec);
|
|
3789
3742
|
} catch {
|
|
@@ -3793,7 +3746,7 @@ async function hydratePath(env = process.env, platform2 = process.platform, exec
|
|
|
3793
3746
|
break;
|
|
3794
3747
|
}
|
|
3795
3748
|
}
|
|
3796
|
-
if (!found &&
|
|
3749
|
+
if (!found && platform === "darwin") {
|
|
3797
3750
|
found = await readLaunchctlPath(exec);
|
|
3798
3751
|
if (found) from = "launchctl";
|
|
3799
3752
|
}
|
|
@@ -3821,7 +3774,6 @@ var TEXT_FILES = {
|
|
|
3821
3774
|
};
|
|
3822
3775
|
var VALID_ACTIONS = new Set(ACTION_TYPES);
|
|
3823
3776
|
var MAX_BODY_BYTES = 64 * 1024;
|
|
3824
|
-
var PRESENCE_POLL_MS = 3e4;
|
|
3825
3777
|
var AGENT_CLOCK_MS = 3e4;
|
|
3826
3778
|
function sendJSON(res, status, body) {
|
|
3827
3779
|
const payload = JSON.stringify(body);
|
|
@@ -4023,13 +3975,11 @@ data: ${payload}
|
|
|
4023
3975
|
() => {
|
|
4024
3976
|
void store.archiveCurrentBrief().then(() => broadcast());
|
|
4025
3977
|
},
|
|
4026
|
-
// The
|
|
4027
|
-
//
|
|
4028
|
-
//
|
|
3978
|
+
// The two boards rewrite prs.json and tickets.json every fetch; that's this
|
|
3979
|
+
// process talking to itself, and both already broadcast when something
|
|
3980
|
+
// actually changed.
|
|
4029
3981
|
{
|
|
4030
3982
|
ignore: [
|
|
4031
|
-
basename(config.sessionFile),
|
|
4032
|
-
basename(tempPathFor(config.sessionFile)),
|
|
4033
3983
|
basename(config.pullsFile),
|
|
4034
3984
|
basename(tempPathFor(config.pullsFile)),
|
|
4035
3985
|
basename(config.ticketsFile),
|
|
@@ -4049,32 +3999,6 @@ data: ${payload}
|
|
|
4049
3999
|
void broadcast();
|
|
4050
4000
|
});
|
|
4051
4001
|
});
|
|
4052
|
-
let sessionTick = null;
|
|
4053
|
-
function scheduleSessionTick(endsAt) {
|
|
4054
|
-
if (sessionTick) clearTimeout(sessionTick);
|
|
4055
|
-
sessionTick = null;
|
|
4056
|
-
if (!endsAt) return;
|
|
4057
|
-
const delay = new Date(endsAt).getTime() - Date.now();
|
|
4058
|
-
if (delay <= 0 || delay > 36e5) return;
|
|
4059
|
-
sessionTick = setTimeout(() => void broadcast(), delay);
|
|
4060
|
-
sessionTick.unref();
|
|
4061
|
-
}
|
|
4062
|
-
async function checkPresence() {
|
|
4063
|
-
let closed;
|
|
4064
|
-
try {
|
|
4065
|
-
closed = await reconcileSession(config, readIdleSeconds);
|
|
4066
|
-
} catch (err) {
|
|
4067
|
-
console.error(`[daily-focus] presence check failed: ${err.message}`);
|
|
4068
|
-
return;
|
|
4069
|
-
}
|
|
4070
|
-
if (!closed) return;
|
|
4071
|
-
console.log(
|
|
4072
|
-
`[daily-focus] stopped "${closed.title}" at ${closed.endedAt} after ${closed.actualMinutes} min \u2014 machine untouched since then`
|
|
4073
|
-
);
|
|
4074
|
-
scheduleSessionTick(null);
|
|
4075
|
-
await broadcast();
|
|
4076
|
-
}
|
|
4077
|
-
await checkPresence();
|
|
4078
4002
|
await assistant.start();
|
|
4079
4003
|
await agent.start();
|
|
4080
4004
|
let ticks = Promise.resolve();
|
|
@@ -4099,7 +4023,6 @@ data: ${payload}
|
|
|
4099
4023
|
void clockTick();
|
|
4100
4024
|
}, AGENT_CLOCK_MS);
|
|
4101
4025
|
agentClock.unref();
|
|
4102
|
-
scheduleSessionTick((await buildState()).session.active?.endsAt ?? null);
|
|
4103
4026
|
void board.start().catch((err) => {
|
|
4104
4027
|
console.error(`[daily-focus] pull request board failed to start: ${err.message}`);
|
|
4105
4028
|
});
|
|
@@ -4113,10 +4036,6 @@ data: ${payload}
|
|
|
4113
4036
|
void broadcast();
|
|
4114
4037
|
}, 6e4);
|
|
4115
4038
|
heartbeat.unref();
|
|
4116
|
-
const presencePoll = setInterval(() => {
|
|
4117
|
-
void checkPresence();
|
|
4118
|
-
}, PRESENCE_POLL_MS);
|
|
4119
|
-
presencePoll.unref();
|
|
4120
4039
|
const server = createServer((req, res) => {
|
|
4121
4040
|
void handle(req, res).catch((err) => {
|
|
4122
4041
|
console.error(`[daily-focus] ${req.method} ${req.url} failed:`, err);
|
|
@@ -4304,8 +4223,8 @@ data: ${JSON.stringify(await buildState())}
|
|
|
4304
4223
|
sendJSON(res, 400, { error: `invalid JSON body: ${err.message}` });
|
|
4305
4224
|
return;
|
|
4306
4225
|
}
|
|
4307
|
-
const { run:
|
|
4308
|
-
if (typeof
|
|
4226
|
+
const { run: run5, text } = typeof body === "object" && body !== null ? body : {};
|
|
4227
|
+
if (typeof run5 !== "string" || run5 === "") {
|
|
4309
4228
|
sendJSON(res, 400, { error: "run must be a run id" });
|
|
4310
4229
|
return;
|
|
4311
4230
|
}
|
|
@@ -4314,7 +4233,7 @@ data: ${JSON.stringify(await buildState())}
|
|
|
4314
4233
|
return;
|
|
4315
4234
|
}
|
|
4316
4235
|
try {
|
|
4317
|
-
await agent.ask(
|
|
4236
|
+
await agent.ask(run5, text);
|
|
4318
4237
|
} catch (err) {
|
|
4319
4238
|
sendJSON(res, 409, { error: err.message });
|
|
4320
4239
|
return;
|
|
@@ -4384,53 +4303,6 @@ data: ${JSON.stringify(await buildState())}
|
|
|
4384
4303
|
void broadcast(state);
|
|
4385
4304
|
return;
|
|
4386
4305
|
}
|
|
4387
|
-
if (path === "/api/session" && req.method === "POST") {
|
|
4388
|
-
let body;
|
|
4389
|
-
try {
|
|
4390
|
-
body = JSON.parse(await readBody(req));
|
|
4391
|
-
} catch (err) {
|
|
4392
|
-
sendJSON(res, 400, { error: `invalid JSON body: ${err.message}` });
|
|
4393
|
-
return;
|
|
4394
|
-
}
|
|
4395
|
-
if (typeof body !== "object" || body === null) {
|
|
4396
|
-
sendJSON(res, 400, { error: "body must be an object" });
|
|
4397
|
-
return;
|
|
4398
|
-
}
|
|
4399
|
-
const { action, id, minutes } = body;
|
|
4400
|
-
if (action === "stop") {
|
|
4401
|
-
await stopSession(config);
|
|
4402
|
-
} else if (action === "start") {
|
|
4403
|
-
if (typeof id !== "string" || id === "") {
|
|
4404
|
-
sendJSON(res, 400, { error: '"id" is required to start a session' });
|
|
4405
|
-
return;
|
|
4406
|
-
}
|
|
4407
|
-
const { brief } = await store.readBrief();
|
|
4408
|
-
const item = brief?.items.find((candidate) => candidate.id === id);
|
|
4409
|
-
if (!item) {
|
|
4410
|
-
sendJSON(res, 404, { error: `no item with id ${id}` });
|
|
4411
|
-
return;
|
|
4412
|
-
}
|
|
4413
|
-
const requested = typeof minutes === "number" && Number.isFinite(minutes) ? Math.round(minutes) : config.sessionMinutes;
|
|
4414
|
-
if (requested < 1 || requested > 120) {
|
|
4415
|
-
sendJSON(res, 400, { error: '"minutes" must be between 1 and 120' });
|
|
4416
|
-
return;
|
|
4417
|
-
}
|
|
4418
|
-
await startSession(config, {
|
|
4419
|
-
id,
|
|
4420
|
-
title: item.title,
|
|
4421
|
-
minutes: requested,
|
|
4422
|
-
advancesObjective: item.advancesObjective === true
|
|
4423
|
-
});
|
|
4424
|
-
} else {
|
|
4425
|
-
sendJSON(res, 400, { error: '"action" must be "start" or "stop"' });
|
|
4426
|
-
return;
|
|
4427
|
-
}
|
|
4428
|
-
const state = await buildState();
|
|
4429
|
-
sendState(res, state);
|
|
4430
|
-
scheduleSessionTick(state.session.active?.endsAt ?? null);
|
|
4431
|
-
void broadcast(state);
|
|
4432
|
-
return;
|
|
4433
|
-
}
|
|
4434
4306
|
if (path === "/api/settings" && req.method === "GET") {
|
|
4435
4307
|
sendJSON(res, 200, { ...describeSettings(readSources()), restart });
|
|
4436
4308
|
return;
|
|
@@ -4558,9 +4430,7 @@ ${entry.source}`)).map(({ title, source }) => ({ title, source })).sort((a, b) =
|
|
|
4558
4430
|
}
|
|
4559
4431
|
async function stopEverything() {
|
|
4560
4432
|
clearInterval(heartbeat);
|
|
4561
|
-
clearInterval(presencePoll);
|
|
4562
4433
|
clearInterval(agentClock);
|
|
4563
|
-
if (sessionTick) clearTimeout(sessionTick);
|
|
4564
4434
|
board.stop();
|
|
4565
4435
|
calendar.stop();
|
|
4566
4436
|
tickets.stop();
|
package/package.json
CHANGED
package/prompts/README.md
CHANGED
|
@@ -37,7 +37,6 @@ alongside `items.schema.json`, so a run only ever names a path inside the store:
|
|
|
37
37
|
settings.json the dashboard writes: what you set on its settings page
|
|
38
38
|
items.json the agent writes it
|
|
39
39
|
actions.jsonl the dashboard appends
|
|
40
|
-
sessions.jsonl the dashboard appends
|
|
41
40
|
agent.jsonl the dashboard appends: every run, its report, and the questions asked of it
|
|
42
41
|
archive/ the dashboard writes
|
|
43
42
|
prs.json the dashboard writes: its last fetch of the user's open pull requests
|
|
@@ -93,8 +92,8 @@ works inside the store. Coding agents can read the implementation and tests, so
|
|
|
93
92
|
| `~/.daily-focus/sources.md` | Which calendars, account and recurring documents are yours. Not in this repo, for the same reason: it's the half of the brief that names real things. |
|
|
94
93
|
|
|
95
94
|
When code changes what the briefing agent must produce or understand, update the
|
|
96
|
-
prompt too. The tests check field names, required fields
|
|
97
|
-
|
|
95
|
+
prompt too. The tests check field names, required fields and enum values. Review
|
|
96
|
+
changes to judgement rules directly.
|
|
98
97
|
|
|
99
98
|
## Why the personal specifics live outside the repo
|
|
100
99
|
|