reflectt-node 0.1.3 → 0.1.5
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 +60 -151
- package/dist/alert-preflight.d.ts +28 -0
- package/dist/alert-preflight.d.ts.map +1 -1
- package/dist/alert-preflight.js +178 -0
- package/dist/alert-preflight.js.map +1 -1
- package/dist/boardHealthWorker.d.ts.map +1 -1
- package/dist/boardHealthWorker.js +25 -12
- package/dist/boardHealthWorker.js.map +1 -1
- package/dist/chat-approval-detector.d.ts.map +1 -1
- package/dist/chat-approval-detector.js +29 -11
- package/dist/chat-approval-detector.js.map +1 -1
- package/dist/chat.d.ts +1 -0
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +14 -3
- package/dist/chat.js.map +1 -1
- package/dist/cli.js +187 -22
- package/dist/cli.js.map +1 -1
- package/dist/cloud.d.ts +28 -1
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +55 -20
- package/dist/cloud.js.map +1 -1
- package/dist/compliance-detector.d.ts +42 -0
- package/dist/compliance-detector.d.ts.map +1 -0
- package/dist/compliance-detector.js +286 -0
- package/dist/compliance-detector.js.map +1 -0
- package/dist/continuity-loop.d.ts.map +1 -1
- package/dist/continuity-loop.js +7 -3
- package/dist/continuity-loop.js.map +1 -1
- package/dist/dashboard.d.ts +6 -2
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/dashboard.js +56 -26
- package/dist/dashboard.js.map +1 -1
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +78 -11
- package/dist/doctor.js.map +1 -1
- package/dist/executionSweeper.d.ts.map +1 -1
- package/dist/executionSweeper.js +12 -0
- package/dist/executionSweeper.js.map +1 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +30 -7
- package/dist/health.js.map +1 -1
- package/dist/hostConnectGuard.d.ts +25 -0
- package/dist/hostConnectGuard.d.ts.map +1 -0
- package/dist/hostConnectGuard.js +27 -0
- package/dist/hostConnectGuard.js.map +1 -0
- package/dist/index.js +144 -29
- package/dist/index.js.map +1 -1
- package/dist/insight-task-bridge.d.ts +22 -1
- package/dist/insight-task-bridge.d.ts.map +1 -1
- package/dist/insight-task-bridge.js +19 -4
- package/dist/insight-task-bridge.js.map +1 -1
- package/dist/mcp.js +6 -6
- package/dist/mcp.js.map +1 -1
- package/dist/notificationDedupeGuard.d.ts +33 -0
- package/dist/notificationDedupeGuard.d.ts.map +1 -0
- package/dist/notificationDedupeGuard.js +88 -0
- package/dist/notificationDedupeGuard.js.map +1 -0
- package/dist/policy.d.ts +1 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +3 -1
- package/dist/policy.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +7 -8
- package/dist/preflight.js.map +1 -1
- package/dist/reflection-automation.d.ts.map +1 -1
- package/dist/reflection-automation.js +38 -0
- package/dist/reflection-automation.js.map +1 -1
- package/dist/request-tracker.d.ts +13 -0
- package/dist/request-tracker.d.ts.map +1 -1
- package/dist/request-tracker.js +95 -16
- package/dist/request-tracker.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +244 -58
- package/dist/server.js.map +1 -1
- package/dist/service-probe.d.ts.map +1 -1
- package/dist/service-probe.js +39 -2
- package/dist/service-probe.js.map +1 -1
- package/dist/shipped-heartbeat.d.ts +1 -1
- package/dist/shipped-heartbeat.js +1 -1
- package/dist/taskPrecheck.js +6 -6
- package/dist/taskPrecheck.js.map +1 -1
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +8 -5
- package/dist/tasks.js.map +1 -1
- package/dist/todoHoardingGuard.d.ts +35 -0
- package/dist/todoHoardingGuard.d.ts.map +1 -0
- package/dist/todoHoardingGuard.js +150 -0
- package/dist/todoHoardingGuard.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/working-contract.d.ts.map +1 -1
- package/dist/working-contract.js +59 -3
- package/dist/working-contract.js.map +1 -1
- package/package.json +1 -1
- package/public/dashboard.js +94 -27
- package/public/docs.md +17 -2
- package/public/intensity-mock.html +413 -0
- package/public/polls-mock.html +610 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) Reflectt AI
|
|
3
|
+
/**
|
|
4
|
+
* Todo Hoarding Guard
|
|
5
|
+
*
|
|
6
|
+
* Prevents idle agents from holding too many todo tasks:
|
|
7
|
+
* Rule A — Cap: if assignee.todo > TODO_CAP && assignee.doing == 0 &&
|
|
8
|
+
* last_activity > IDLE_THRESHOLD_MS, auto-unassign lowest-priority
|
|
9
|
+
* todos beyond top TODO_CAP.
|
|
10
|
+
* Rule B — Orphan: mark todos held by idle/offline agents as orphaned
|
|
11
|
+
* so they don't count toward ready-floor supply.
|
|
12
|
+
* Rule C — Claim: /tasks/next?claim=1 auto-transitions todo→doing.
|
|
13
|
+
*/
|
|
14
|
+
import { taskManager } from './tasks.js';
|
|
15
|
+
// ── Config ─────────────────────────────────────────────────────────────────
|
|
16
|
+
/** Max todo tasks per agent before auto-unassign kicks in */
|
|
17
|
+
export const TODO_CAP = 3;
|
|
18
|
+
/** Agent must be idle for this long (no doing tasks + no activity) before unassign */
|
|
19
|
+
export const IDLE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
|
|
20
|
+
/** Priority ordering (lower index = higher priority, kept first) */
|
|
21
|
+
const PRIORITY_ORDER = ['P0', 'P1', 'P2', 'P3'];
|
|
22
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
23
|
+
function priorityRank(p) {
|
|
24
|
+
const idx = PRIORITY_ORDER.indexOf(p);
|
|
25
|
+
return idx >= 0 ? idx : PRIORITY_ORDER.length;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the most recent activity timestamp for an agent.
|
|
29
|
+
* Activity = last task update.
|
|
30
|
+
*/
|
|
31
|
+
function getAgentLastActivity(agent, allTasks) {
|
|
32
|
+
let latest = 0;
|
|
33
|
+
for (const t of allTasks) {
|
|
34
|
+
if (t.assignee?.toLowerCase() !== agent.toLowerCase())
|
|
35
|
+
continue;
|
|
36
|
+
const ts = typeof t.updatedAt === 'number' ? t.updatedAt : 0;
|
|
37
|
+
if (ts > latest)
|
|
38
|
+
latest = ts;
|
|
39
|
+
}
|
|
40
|
+
return latest;
|
|
41
|
+
}
|
|
42
|
+
// ── Core Logic ─────────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Run the hoarding sweep. Returns actions taken (or would be taken in dry-run).
|
|
45
|
+
*/
|
|
46
|
+
export async function sweepTodoHoarding(opts = {}) {
|
|
47
|
+
const { dryRun = false } = opts;
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const allTasks = taskManager.listTasks();
|
|
50
|
+
// Group by assignee
|
|
51
|
+
const byAssignee = new Map();
|
|
52
|
+
for (const t of allTasks) {
|
|
53
|
+
if (!t.assignee || t.assignee === 'unassigned')
|
|
54
|
+
continue;
|
|
55
|
+
const agent = t.assignee.toLowerCase();
|
|
56
|
+
if (!byAssignee.has(agent))
|
|
57
|
+
byAssignee.set(agent, { todo: [], doing: [] });
|
|
58
|
+
const bucket = byAssignee.get(agent);
|
|
59
|
+
if (t.status === 'todo')
|
|
60
|
+
bucket.todo.push(t);
|
|
61
|
+
else if (t.status === 'doing')
|
|
62
|
+
bucket.doing.push(t);
|
|
63
|
+
}
|
|
64
|
+
const unassigned = [];
|
|
65
|
+
const orphaned = [];
|
|
66
|
+
for (const [agent, { todo, doing }] of byAssignee) {
|
|
67
|
+
const lastActivity = getAgentLastActivity(agent, allTasks);
|
|
68
|
+
const idleMs = now - lastActivity;
|
|
69
|
+
// Rule B: orphan detection — flag todos held by agents with 0 doing AND idle
|
|
70
|
+
if (doing.length === 0 && todo.length > 0 && idleMs >= IDLE_THRESHOLD_MS) {
|
|
71
|
+
for (const t of todo) {
|
|
72
|
+
orphaned.push({
|
|
73
|
+
taskId: t.id,
|
|
74
|
+
taskTitle: t.title || '',
|
|
75
|
+
assignee: agent,
|
|
76
|
+
idleMinutes: Math.round(idleMs / 60000),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Rule A: auto-unassign overflow
|
|
81
|
+
// Skip agents actively doing work
|
|
82
|
+
if (doing.length > 0)
|
|
83
|
+
continue;
|
|
84
|
+
// Skip agents with todo at or below cap
|
|
85
|
+
if (todo.length <= TODO_CAP)
|
|
86
|
+
continue;
|
|
87
|
+
// Only unassign if idle beyond threshold
|
|
88
|
+
if (idleMs < IDLE_THRESHOLD_MS)
|
|
89
|
+
continue;
|
|
90
|
+
// Sort by priority (keep highest priority), then by createdAt (keep newest)
|
|
91
|
+
const sorted = [...todo].sort((a, b) => {
|
|
92
|
+
const pDiff = priorityRank(a.priority || 'P3') - priorityRank(b.priority || 'P3');
|
|
93
|
+
if (pDiff !== 0)
|
|
94
|
+
return pDiff;
|
|
95
|
+
// Same priority: keep more recently created
|
|
96
|
+
return (b.createdAt || 0) - (a.createdAt || 0);
|
|
97
|
+
});
|
|
98
|
+
// Keep top TODO_CAP, unassign the rest
|
|
99
|
+
const toUnassign = sorted.slice(TODO_CAP);
|
|
100
|
+
for (const task of toUnassign) {
|
|
101
|
+
// Skip pinned tasks
|
|
102
|
+
if (task.metadata?.pinned)
|
|
103
|
+
continue;
|
|
104
|
+
const reason = `Auto-unassigned: ${agent} held ${todo.length} todos with 0 doing and ${Math.round(idleMs / 60000)}m idle (cap: ${TODO_CAP})`;
|
|
105
|
+
if (!dryRun) {
|
|
106
|
+
await taskManager.updateTask(task.id, {
|
|
107
|
+
assignee: 'unassigned',
|
|
108
|
+
});
|
|
109
|
+
// Add comment for audit trail
|
|
110
|
+
await taskManager.addTaskComment(task.id, 'system', `🔄 ${reason}`);
|
|
111
|
+
}
|
|
112
|
+
unassigned.push({
|
|
113
|
+
taskId: task.id,
|
|
114
|
+
taskTitle: task.title || '',
|
|
115
|
+
previousAssignee: agent,
|
|
116
|
+
reason,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
unassigned,
|
|
122
|
+
orphaned,
|
|
123
|
+
scanned: allTasks.length,
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Claim a task: transition from todo → doing when fetched via /tasks/next.
|
|
129
|
+
* Returns the updated task or null if transition fails.
|
|
130
|
+
*/
|
|
131
|
+
export async function claimTask(taskId, agent) {
|
|
132
|
+
const task = taskManager.getTask(taskId);
|
|
133
|
+
if (!task)
|
|
134
|
+
return null;
|
|
135
|
+
if (task.status !== 'todo')
|
|
136
|
+
return null;
|
|
137
|
+
const updated = await taskManager.updateTask(taskId, {
|
|
138
|
+
status: 'doing',
|
|
139
|
+
assignee: agent,
|
|
140
|
+
metadata: {
|
|
141
|
+
...(task.metadata || {}),
|
|
142
|
+
eta: '~60m (auto-claimed)',
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
if (updated) {
|
|
146
|
+
await taskManager.addTaskComment(taskId, 'system', `📋 Auto-claimed by ${agent} via /tasks/next?claim=1`);
|
|
147
|
+
}
|
|
148
|
+
return updated || null;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=todoHoardingGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todoHoardingGuard.js","sourceRoot":"","sources":["../src/todoHoardingGuard.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface Task {
|
|
|
25
25
|
id: string;
|
|
26
26
|
title: string;
|
|
27
27
|
description?: string;
|
|
28
|
-
status: 'todo' | 'doing' | 'blocked' | 'validating' | 'done';
|
|
28
|
+
status: 'todo' | 'doing' | 'blocked' | 'validating' | 'done' | 'resolved_externally' | 'cancelled';
|
|
29
29
|
assignee?: string;
|
|
30
30
|
reviewer?: string;
|
|
31
31
|
done_criteria?: string[];
|
|
@@ -88,7 +88,7 @@ export interface RecurringTask {
|
|
|
88
88
|
metadata?: Record<string, unknown>;
|
|
89
89
|
schedule: RecurringTaskSchedule;
|
|
90
90
|
enabled: boolean;
|
|
91
|
-
status?: 'todo' | 'doing' | 'blocked' | 'validating' | 'done';
|
|
91
|
+
status?: 'todo' | 'doing' | 'blocked' | 'validating' | 'done' | 'resolved_externally' | 'cancelled';
|
|
92
92
|
lastRunAt?: number;
|
|
93
93
|
lastSkipAt?: number;
|
|
94
94
|
lastSkipReason?: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;CAC/B;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"working-contract.d.ts","sourceRoot":"","sources":["../src/working-contract.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,OAAO,CAAA;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,uBAAuB,CAAA;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;
|
|
1
|
+
{"version":3,"file":"working-contract.d.ts","sourceRoot":"","sources":["../src/working-contract.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,OAAO,CAAA;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,uBAAuB,CAAA;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AA6DD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,UAAU,CAAC,CAmH/D;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAkE7D;AAqCD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,wBAAgB,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAElD"}
|
package/dist/working-contract.js
CHANGED
|
@@ -14,7 +14,60 @@ import { routeMessage } from './messageRouter.js';
|
|
|
14
14
|
import { listReflections } from './reflections.js';
|
|
15
15
|
import { getEffectiveActivity, formatActivityWarning } from './activity-signal.js';
|
|
16
16
|
// ── State: track warnings ──
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* warningTimestamps: key → epoch ms when Phase 1 warning was issued.
|
|
19
|
+
*
|
|
20
|
+
* Backed by SQLite so restarts don't re-fire warnings. In-memory cache
|
|
21
|
+
* is seeded from DB on first use.
|
|
22
|
+
*
|
|
23
|
+
* Root cause of compliance snapshot 3x bug: this was previously a plain
|
|
24
|
+
* in-memory Map that reset on every server restart. Each restart would
|
|
25
|
+
* re-fire the Phase 1 warning for every stale doing task. The stale
|
|
26
|
+
* duration increments between restarts (e.g. "stale for 45m" vs "46m"),
|
|
27
|
+
* which bypassed chat.ts content dedup, resulting in 2–3x identical-
|
|
28
|
+
* looking warning messages per agent per deploy cycle.
|
|
29
|
+
*/
|
|
30
|
+
const warningTimestamps = new Map();
|
|
31
|
+
let _warningDbSeeded = false;
|
|
32
|
+
function ensureWarningTable() {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
db.prepare(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS wc_warning_timestamps (
|
|
36
|
+
key TEXT PRIMARY KEY,
|
|
37
|
+
warned_at INTEGER NOT NULL
|
|
38
|
+
)
|
|
39
|
+
`).run();
|
|
40
|
+
}
|
|
41
|
+
function seedWarningTimestamps() {
|
|
42
|
+
if (_warningDbSeeded)
|
|
43
|
+
return;
|
|
44
|
+
_warningDbSeeded = true;
|
|
45
|
+
try {
|
|
46
|
+
ensureWarningTable();
|
|
47
|
+
const db = getDb();
|
|
48
|
+
const rows = db.prepare('SELECT key, warned_at FROM wc_warning_timestamps').all();
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
warningTimestamps.set(row.key, row.warned_at);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* db may not be ready */ }
|
|
54
|
+
}
|
|
55
|
+
function persistWarning(key, timestamp) {
|
|
56
|
+
try {
|
|
57
|
+
ensureWarningTable();
|
|
58
|
+
const db = getDb();
|
|
59
|
+
db.prepare('INSERT OR REPLACE INTO wc_warning_timestamps (key, warned_at) VALUES (?, ?)').run(key, timestamp);
|
|
60
|
+
}
|
|
61
|
+
catch { /* best-effort */ }
|
|
62
|
+
}
|
|
63
|
+
function clearWarning(key) {
|
|
64
|
+
warningTimestamps.delete(key);
|
|
65
|
+
try {
|
|
66
|
+
const db = getDb();
|
|
67
|
+
db.prepare('DELETE FROM wc_warning_timestamps WHERE key = ?').run(key);
|
|
68
|
+
}
|
|
69
|
+
catch { /* best-effort */ }
|
|
70
|
+
}
|
|
18
71
|
// ── Enforcement tick ──
|
|
19
72
|
/**
|
|
20
73
|
* Called periodically. Checks all 'doing' tasks for stale status.
|
|
@@ -24,6 +77,8 @@ export async function tickWorkingContract() {
|
|
|
24
77
|
const config = getConfig();
|
|
25
78
|
if (!config.enabled)
|
|
26
79
|
return { warnings: 0, requeued: 0, actions: [] };
|
|
80
|
+
// Seed warningTimestamps from DB on first tick (survives process restarts)
|
|
81
|
+
seedWarningTimestamps();
|
|
27
82
|
const now = Date.now();
|
|
28
83
|
const staleThresholdMs = config.staleAutoRequeueMin * 60_000;
|
|
29
84
|
const graceMs = config.graceAfterWarningMin * 60_000;
|
|
@@ -51,6 +106,7 @@ export async function tickWorkingContract() {
|
|
|
51
106
|
if (!warnedAt) {
|
|
52
107
|
// Phase 1: Issue warning
|
|
53
108
|
warningTimestamps.set(warningKey, now);
|
|
109
|
+
persistWarning(warningKey, now);
|
|
54
110
|
const signalInfo = formatActivityWarning(activitySignal, config.staleAutoRequeueMin, now);
|
|
55
111
|
const action = {
|
|
56
112
|
type: 'warning',
|
|
@@ -80,7 +136,7 @@ export async function tickWorkingContract() {
|
|
|
80
136
|
const activitySinceWarning = getLastActivityForAgent(task.id, agent);
|
|
81
137
|
if (activitySinceWarning && activitySinceWarning > warnedAt) {
|
|
82
138
|
// Agent responded — clear warning
|
|
83
|
-
|
|
139
|
+
clearWarning(warningKey);
|
|
84
140
|
continue;
|
|
85
141
|
}
|
|
86
142
|
// Auto-requeue
|
|
@@ -95,7 +151,7 @@ export async function tickWorkingContract() {
|
|
|
95
151
|
};
|
|
96
152
|
actions.push(action);
|
|
97
153
|
requeued++;
|
|
98
|
-
|
|
154
|
+
clearWarning(warningKey);
|
|
99
155
|
if (!config.dryRun) {
|
|
100
156
|
try {
|
|
101
157
|
// Move task back to todo, clear assignee
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"working-contract.js","sourceRoot":"","sources":["../src/working-contract.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,+BAA+B;AAC/B,EAAE;AACF,sEAAsE;AACtE,kGAAkG;AAClG,uFAAuF;AACvF,4EAA4E;AAC5E,EAAE;AACF,+DAA+D;AAE/D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAuB,MAAM,sBAAsB,CAAA;AAqCvG,8BAA8B;AAE9B,MAAM,iBAAiB,GAAwB,IAAI,GAAG,EAAE,CAAA,CAAC,
|
|
1
|
+
{"version":3,"file":"working-contract.js","sourceRoot":"","sources":["../src/working-contract.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,+BAA+B;AAC/B,EAAE;AACF,sEAAsE;AACtE,kGAAkG;AAClG,uFAAuF;AACvF,4EAA4E;AAC5E,EAAE;AACF,+DAA+D;AAE/D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAuB,MAAM,sBAAsB,CAAA;AAqCvG,8BAA8B;AAE9B;;;;;;;;;;;;GAYG;AACH,MAAM,iBAAiB,GAAwB,IAAI,GAAG,EAAE,CAAA;AACxD,IAAI,gBAAgB,GAAG,KAAK,CAAA;AAE5B,SAAS,kBAAkB;IACzB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,EAAE,CAAC,OAAO,CAAC;;;;;GAKV,CAAC,CAAC,GAAG,EAAE,CAAA;AACV,CAAC;AAED,SAAS,qBAAqB;IAC5B,IAAI,gBAAgB;QAAE,OAAM;IAC5B,gBAAgB,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC;QACH,kBAAkB,EAAE,CAAA;QACpB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;QAClB,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC,GAAG,EAA0C,CAAA;QACzH,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,cAAc,CAAC,GAAW,EAAE,SAAiB;IACpD,IAAI,CAAC;QACH,kBAAkB,EAAE,CAAA;QACpB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;QAClB,EAAE,CAAC,OAAO,CAAC,6EAA6E,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IAC/G,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;QAClB,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxE,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAC/B,CAAC;AAED,yBAAyB;AAEzB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IAErE,2EAA2E;IAC3E,qBAAqB,EAAE,CAAA;IAEvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,GAAG,MAAM,CAAA;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,GAAG,MAAM,CAAA;IACpD,MAAM,OAAO,GAAwB,EAAE,CAAA;IACvC,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAEhB,2BAA2B;IAC3B,MAAM,UAAU,GAAG,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;IAE7D,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC3B,IAAI,CAAC,KAAK;YAAE,SAAQ;QACpB,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAQ;QACxE,IAAI,eAAe,CAAC,KAAK,CAAC;YAAE,SAAQ;QAEpC,8EAA8E;QAC9E,MAAM,cAAc,GAAG,oBAAoB,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,CAAA;QAClF,MAAM,eAAe,GAAG,cAAc,CAAC,mBAAmB,CAAA;QAC1D,MAAM,eAAe,GAAG,GAAG,GAAG,eAAe,CAAA;QAE7C,IAAI,eAAe,GAAG,gBAAgB;YAAE,SAAQ;QAEhD,MAAM,UAAU,GAAG,GAAG,KAAK,IAAI,IAAI,CAAC,EAAE,EAAE,CAAA;QACxC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QAElD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,yBAAyB;YACzB,iBAAiB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;YACtC,cAAc,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;YAC/B,MAAM,UAAU,GAAG,qBAAqB,CAAC,cAAc,EAAE,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAA;YACzF,MAAM,MAAM,GAAsB;gBAChC,IAAI,EAAE,SAAS;gBACf,KAAK;gBACL,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,SAAS,EAAE,IAAI,CAAC,KAAK;gBACrB,MAAM,EAAE,UAAU,UAAU,+BAA+B,MAAM,CAAC,oBAAoB,iBAAiB;gBACvG,SAAS,EAAE,GAAG;gBACd,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAA;YACD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpB,QAAQ,EAAE,CAAA;YAEV,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,MAAM,YAAY,CAAC;oBACjB,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,6BAA6B,KAAK,UAAU,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,UAAU,oCAAoC,MAAM,CAAC,oBAAoB,+FAA+F;oBACzQ,QAAQ,EAAE,gBAAgB;oBAC1B,QAAQ,EAAE,SAAS;oBACnB,YAAY,EAAE,MAAM,CAAC,OAAO;oBAC5B,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,QAAQ,EAAE,CAAC,KAAK,CAAC;iBAClB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACpB,CAAC;QACH,CAAC;aAAM,IAAI,GAAG,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC;YACrC,+CAA+C;YAC/C,MAAM,oBAAoB,GAAG,uBAAuB,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;YACpE,IAAI,oBAAoB,IAAI,oBAAoB,GAAG,QAAQ,EAAE,CAAC;gBAC5D,kCAAkC;gBAClC,YAAY,CAAC,UAAU,CAAC,CAAA;gBACxB,SAAQ;YACV,CAAC;YAED,eAAe;YACf,MAAM,MAAM,GAAsB;gBAChC,IAAI,EAAE,cAAc;gBACpB,KAAK;gBACL,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,SAAS,EAAE,IAAI,CAAC,KAAK;gBACrB,MAAM,EAAE,6DAA6D;gBACrE,SAAS,EAAE,GAAG;gBACd,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAA;YACD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpB,QAAQ,EAAE,CAAA;YACV,YAAY,CAAC,UAAU,CAAC,CAAA;YAExB,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,IAAI,CAAC;oBACH,yCAAyC;oBACzC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;oBAClB,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC;yBACnE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;oBAE5B,2FAA2F;oBAC3F,MAAM,WAAW,CAAC,cAAc,CAC9B,IAAI,CAAC,EAAE,EACP,QAAQ,EACR,kEAAkE,KAAK,qFAAqF,CAC7J,CAAA;oBAED,MAAM,YAAY,CAAC;wBACjB,IAAI,EAAE,QAAQ;wBACd,OAAO,EAAE,+CAA+C,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,gCAAgC,KAAK,2BAA2B,MAAM,CAAC,oBAAoB,mEAAmE;wBAC1P,QAAQ,EAAE,gBAAgB;wBAC1B,QAAQ,EAAE,SAAS;wBACnB,YAAY,EAAE,MAAM,CAAC,OAAO;wBAC5B,MAAM,EAAE,IAAI,CAAC,EAAE;wBACf,QAAQ,EAAE,CAAC,KAAK,CAAC;qBAClB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;gBACpB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,KAAK,CAAC,6CAA6C,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;gBAC7E,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;AACxC,CAAC;AAED,2CAA2C;AAE3C;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;QACrD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC1B,CAAC;IAED,uBAAuB;IACvB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,IAAI,CAAC;QACH,oFAAoF;QACpF,EAAE,CAAC,IAAI,CAAC;;;;;;;;KAQP,CAAC,CAAA;QACF,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAQ,CAAA;QAClG,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA,CAAC,uCAAuC;QAE/E,MAAM,cAAc,GAAG,QAAQ,CAAC,kBAAkB,IAAI,CAAC,CAAA;QACvD,MAAM,SAAS,GAAG,QAAQ,CAAC,2BAA2B,IAAI,CAAC,CAAA;QAE3D,4EAA4E;QAC5E,MAAM,oBAAoB,GAAG,cAAc,GAAG,CAAC;YAC7C,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC;YAClD,CAAC,CAAC,QAAQ,CAAA;QAEZ,IAAI,SAAS,IAAI,CAAC,IAAI,oBAAoB,GAAG,CAAC,EAAE,CAAC;YAC/C,8FAA8F;YAC9F,yFAAyF;YACzF,mDAAmD;YACnD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC9D,MAAM,QAAQ,GAAG,MAAM,EAAE,UAAU,CAAA;gBACnC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,GAAG,cAAc,EAAE,CAAC;oBAC9D,0EAA0E;oBAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;oBACtB,EAAE,CAAC,OAAO,CAAC;;;;;;;WAOV,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;oBAC3C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;gBAC1B,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,sDAAsD;YACxD,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,oBAAoB,SAAS,2CAA2C,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,wEAAwE;gBACjO,IAAI,EAAE,oBAAoB;gBAC1B,cAAc,EAAE,SAAS;aAC1B,CAAA;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0BAA0B;IAC5B,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,gBAAgB;AAEhB,SAAS,SAAS;IAChB,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,EAAE,CAAA;IAClC,OAAQ,MAAc,CAAC,eAAe,IAAI;QACxC,OAAO,EAAE,IAAI;QACb,mBAAmB,EAAE,EAAE;QACvB,oBAAoB,EAAE,EAAE;QACxB,qBAAqB,EAAE,IAAI;QAC3B,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,SAAS;QAClB,MAAM,EAAE,KAAK;KACd,CAAA;AACH,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAc,EAAE,KAAa;IAC5D,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;QAClB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CACpB,gIAAgI,CACjI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAA0C,CAAA;QAC7D,OAAO,GAAG,EAAE,MAAM,IAAI,IAAI,CAAA;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;AAE1E,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AACnD,CAAC;AAED,qBAAqB;AAErB,MAAM,UAAU,cAAc;IAC5B,iBAAiB,CAAC,KAAK,EAAE,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAA;AACnC,CAAC"}
|
package/package.json
CHANGED
package/public/dashboard.js
CHANGED
|
@@ -71,8 +71,14 @@ function updateFirstBootBanner() {
|
|
|
71
71
|
const el = document.getElementById('first-boot-banner');
|
|
72
72
|
if (!el) return;
|
|
73
73
|
const dismissed = localStorage.getItem('reflectt-first-boot-dismissed');
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
// We seed a welcome task on first boot, so `allTasks.length > 0` is not a good proxy for
|
|
75
|
+
// "user has started using the system". Only hide the banner once a *non-seeded* task exists.
|
|
76
|
+
const seededSources = new Set(['first-boot', 'first-boot-intent']);
|
|
77
|
+
const hasRealTasks = allTasks.some(t => {
|
|
78
|
+
const src = t && t.metadata && t.metadata.source;
|
|
79
|
+
return !src || !seededSources.has(src);
|
|
80
|
+
});
|
|
81
|
+
el.hidden = dismissed === '1' || hasRealTasks;
|
|
76
82
|
}
|
|
77
83
|
function dismissFirstBootBanner() {
|
|
78
84
|
localStorage.setItem('reflectt-first-boot-dismissed', '1');
|
|
@@ -80,6 +86,14 @@ function dismissFirstBootBanner() {
|
|
|
80
86
|
if (el) el.hidden = true;
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
function updateOverviewEmptyState() {
|
|
90
|
+
const el = document.getElementById('overview-empty-state');
|
|
91
|
+
if (!el) return;
|
|
92
|
+
const hasTasks = allTasks.length > 0;
|
|
93
|
+
const hasMessages = allMessages.length > 0;
|
|
94
|
+
el.style.display = (hasTasks || hasMessages) ? 'none' : '';
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
// Delta cursors for lower payload refreshes
|
|
84
98
|
let lastTaskSync = 0;
|
|
85
99
|
let lastChatSync = 0;
|
|
@@ -164,6 +178,14 @@ function formatProductiveText(agent) {
|
|
|
164
178
|
function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
|
|
165
179
|
function formatBytes(b) { if (!b || b < 1024) return b + ' B'; if (b < 1048576) return (b/1024).toFixed(1) + ' KB'; return (b/1048576).toFixed(1) + ' MB'; }
|
|
166
180
|
function truncate(s, n) { return s && s.length > n ? s.slice(0, n) + '…' : (s || ''); }
|
|
181
|
+
function humanizeMinutes(m) {
|
|
182
|
+
if (m == null || m === '') return '—';
|
|
183
|
+
const n = Number(m);
|
|
184
|
+
if (!Number.isFinite(n) || n >= 9999) return '—';
|
|
185
|
+
if (n < 60) return n + 'm';
|
|
186
|
+
if (n < 1440) return Math.round(n / 60) + 'h';
|
|
187
|
+
return Math.round(n / 1440) + 'd';
|
|
188
|
+
}
|
|
167
189
|
function renderTaskTags(tags) {
|
|
168
190
|
if (!Array.isArray(tags) || tags.length === 0) return '';
|
|
169
191
|
const shown = tags.filter(Boolean).slice(0, 3);
|
|
@@ -250,7 +272,7 @@ function renderLaneTransitionMeta(task) {
|
|
|
250
272
|
return `<div style="margin-top:6px;font-size:11px;color:var(--text-muted)">🧭 ${esc(parts.join(' · '))}</div>`;
|
|
251
273
|
}
|
|
252
274
|
|
|
253
|
-
function
|
|
275
|
+
function hasMention(message) { return /@([a-zA-Z][a-zA-Z0-9_-]*)/i.test(message || ''); }
|
|
254
276
|
|
|
255
277
|
function resolveSSOTState(lastVerifiedUtc) {
|
|
256
278
|
if (!lastVerifiedUtc) return { state: 'unknown', label: 'unknown', text: 'verification timestamp unavailable' };
|
|
@@ -442,12 +464,12 @@ function formatDurationMin(min) {
|
|
|
442
464
|
|
|
443
465
|
function statusTemplateFor(agent, taskId) {
|
|
444
466
|
const mentions = agent === 'pixel'
|
|
445
|
-
? '@
|
|
467
|
+
? '@owner @link'
|
|
446
468
|
: agent === 'link'
|
|
447
|
-
? '@
|
|
469
|
+
? '@owner @pixel'
|
|
448
470
|
: agent === 'kai'
|
|
449
471
|
? '@link @pixel'
|
|
450
|
-
: '@
|
|
472
|
+
: '@owner @pixel';
|
|
451
473
|
return [
|
|
452
474
|
mentions,
|
|
453
475
|
'Task: ' + (taskId || '<task-id>'),
|
|
@@ -495,7 +517,7 @@ function renderCompliance(compliance) {
|
|
|
495
517
|
|
|
496
518
|
const chipsHtml = chips.map(c => {
|
|
497
519
|
const state = complianceState(c.value, c.threshold);
|
|
498
|
-
return '<div class="sla-chip ' + state + '"><span>' + esc(c.label) + '</span><strong>' +
|
|
520
|
+
return '<div class="sla-chip ' + state + '"><span>' + esc(c.label) + '</span><strong>' + humanizeMinutes(c.value) + '</strong></div>';
|
|
499
521
|
}).join('');
|
|
500
522
|
|
|
501
523
|
const rows = agents.map(a => {
|
|
@@ -504,7 +526,7 @@ function renderCompliance(compliance) {
|
|
|
504
526
|
return '<tr>' +
|
|
505
527
|
'<td>' + esc(a.agent) + '</td>' +
|
|
506
528
|
'<td>' + taskCell + '</td>' +
|
|
507
|
-
'<td>' +
|
|
529
|
+
'<td>' + humanizeMinutes(a.lastValidStatusAgeMin) + '</td>' +
|
|
508
530
|
'<td>' + a.expectedCadenceMin + 'm</td>' +
|
|
509
531
|
'<td><span class="state-pill ' + a.state + ' compliance-state-' + a.state + '">' + esc(a.state) + '</span></td>' +
|
|
510
532
|
'<td><button class="copy-template-btn" data-agent="' + esc(a.agent) + '" data-task="' + esc(taskValue) + '" onclick="copyStatusTemplate(this.dataset.agent, this.dataset.task)">Copy template</button></td>' +
|
|
@@ -709,6 +731,7 @@ async function loadTasks(forceFull = false) {
|
|
|
709
731
|
const navTaskBadge = document.getElementById('nav-task-count');
|
|
710
732
|
if (navTaskBadge) navTaskBadge.textContent = allTasks.length;
|
|
711
733
|
updateFirstBootBanner();
|
|
734
|
+
updateOverviewEmptyState();
|
|
712
735
|
}
|
|
713
736
|
|
|
714
737
|
function renderProjectTabs() {
|
|
@@ -733,7 +756,7 @@ function toggleTestTasks() {
|
|
|
733
756
|
renderStatusFilterTabs();
|
|
734
757
|
renderKanban();
|
|
735
758
|
// Update task count
|
|
736
|
-
const openCount = getVisibleTasks().filter(t =>
|
|
759
|
+
const openCount = getVisibleTasks().filter(t => !['done', 'resolved_externally', 'cancelled'].includes(t.status)).length;
|
|
737
760
|
document.getElementById('task-count').textContent = currentStatusFilter === 'open'
|
|
738
761
|
? openCount + ' open tasks'
|
|
739
762
|
: getVisibleTasks().length + ' tasks';
|
|
@@ -771,20 +794,47 @@ function renderKanban() {
|
|
|
771
794
|
const filtered = currentProject === 'all' ? visible : visible.filter(t => classifyProject(t) === currentProject);
|
|
772
795
|
const cols = currentStatusFilter === 'open'
|
|
773
796
|
? ['todo', 'doing', 'blocked', 'validating']
|
|
774
|
-
: ['todo', 'doing', 'blocked', 'validating', 'done'];
|
|
797
|
+
: ['todo', 'doing', 'blocked', 'validating', 'done', 'resolved_externally', 'cancelled'];
|
|
775
798
|
const grouped = {}; cols.forEach(c => grouped[c] = []);
|
|
776
|
-
|
|
799
|
+
const tasksForBoard = currentStatusFilter === 'open'
|
|
800
|
+
? filtered.filter(t => cols.includes(t.status || 'todo'))
|
|
801
|
+
: filtered;
|
|
802
|
+
tasksForBoard.forEach(t => { const s = t.status || 'todo'; if (grouped[s]) grouped[s].push(t); else grouped['todo'].push(t); });
|
|
777
803
|
const pOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
778
804
|
cols.forEach(c => grouped[c].sort((a, b) => (pOrder[a.priority] ?? 9) - (pOrder[b.priority] ?? 9)));
|
|
779
805
|
|
|
780
806
|
const kanban = document.getElementById('kanban');
|
|
807
|
+
|
|
808
|
+
// Full-board empty state: no tasks at all
|
|
809
|
+
const totalOnBoard = tasksForBoard.length;
|
|
810
|
+
if (totalOnBoard === 0) {
|
|
811
|
+
kanban.innerHTML = '<div class="board-empty-state" style="grid-column:1/-1;text-align:center;padding:40px 20px;color:var(--text-muted)">'
|
|
812
|
+
+ '<div style="font-size:28px;margin-bottom:12px">📋</div>'
|
|
813
|
+
+ '<div style="font-size:15px;font-weight:500;color:var(--text-bright);margin-bottom:6px">No tasks yet</div>'
|
|
814
|
+
+ '<div style="font-size:13px;max-width:340px;margin:0 auto;line-height:1.5">Create your first task via the API, or connect an AI agent to start generating work automatically.</div>'
|
|
815
|
+
+ '<div style="margin-top:14px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap">'
|
|
816
|
+
+ '<a href="/docs" target="_blank" style="color:var(--accent);font-size:12px;text-decoration:none">API reference →</a>'
|
|
817
|
+
+ '<a href="/capabilities" target="_blank" style="color:var(--accent);font-size:12px;text-decoration:none">Capabilities →</a>'
|
|
818
|
+
+ '</div></div>';
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
781
822
|
kanban.innerHTML = cols.map(col => {
|
|
782
823
|
const items = grouped[col];
|
|
783
824
|
const isDone = col === 'done';
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
:
|
|
825
|
+
const isTodo = col === 'todo';
|
|
826
|
+
const colLimit = isDone ? 3 : isTodo ? 10 : items.length;
|
|
827
|
+
const emptyMessages = {
|
|
828
|
+
todo: 'No tasks queued',
|
|
829
|
+
doing: 'Nothing in progress',
|
|
830
|
+
blocked: 'Nothing blocked',
|
|
831
|
+
validating: 'Nothing awaiting review',
|
|
832
|
+
done: 'No completed tasks yet',
|
|
833
|
+
};
|
|
834
|
+
const cards = items.length === 0
|
|
835
|
+
? '<div class="empty" style="font-size:12px;padding:12px 8px">' + (emptyMessages[col] || '—') + '</div>'
|
|
836
|
+
: items.map((t, idx) => {
|
|
837
|
+
const isHidden = idx >= colLimit;
|
|
788
838
|
const assigneeAgent = t.assignee ? AGENTS.find(a => a.name === t.assignee) : null;
|
|
789
839
|
const assigneeDisplay = t.assignee
|
|
790
840
|
? `<span class="assignee-tag">👤 ${esc(t.assignee)}${assigneeAgent ? ' <span class="role-small">' + esc(assigneeAgent.role) + '</span>' : ''}</span>`
|
|
@@ -793,7 +843,7 @@ function renderKanban() {
|
|
|
793
843
|
? `<div style="margin-top:4px"><span class="assignee-tag" style="font-family:monospace;font-size:10px;color:var(--accent)">🌿 ${esc(t.metadata.branch)}</span></div>`
|
|
794
844
|
: '';
|
|
795
845
|
return `
|
|
796
|
-
<div class="task-card" data-task-id="${t.id}">
|
|
846
|
+
<div class="task-card${isHidden ? ' hidden' : ''}" data-task-id="${t.id}">
|
|
797
847
|
<div class="task-title">${esc(truncate(t.title, 60))}</div>
|
|
798
848
|
<div class="task-meta">
|
|
799
849
|
${t.priority ? '<span class="priority-badge ' + t.priority + '">' + t.priority + '</span>' : ''}
|
|
@@ -808,8 +858,9 @@ function renderKanban() {
|
|
|
808
858
|
${renderQaContract(t)}
|
|
809
859
|
</div>`;
|
|
810
860
|
}).join('');
|
|
811
|
-
const
|
|
812
|
-
|
|
861
|
+
const hasMore = items.length > colLimit;
|
|
862
|
+
const extra = hasMore
|
|
863
|
+
? `<button class="done-toggle" onclick="this.parentElement.querySelectorAll('.task-card.hidden').forEach(c=>c.classList.remove('hidden'));this.remove()">+ ${items.length - colLimit} more</button>` : '';
|
|
813
864
|
return `<div class="kanban-col" data-status="${col}">
|
|
814
865
|
<div class="kanban-col-header">${col} <span class="cnt">${items.length}</span></div>
|
|
815
866
|
${cards}${extra}
|
|
@@ -1021,13 +1072,13 @@ async function loadChat(forceFull = false) {
|
|
|
1021
1072
|
if (!channelStats.has(ch)) channelStats.set(ch, { total: 0, mentions: 0 });
|
|
1022
1073
|
const stats = channelStats.get(ch);
|
|
1023
1074
|
stats.total += 1;
|
|
1024
|
-
if (
|
|
1075
|
+
if (hasMention(m.content)) stats.mentions += 1;
|
|
1025
1076
|
});
|
|
1026
1077
|
|
|
1027
1078
|
const tabs = document.getElementById('channel-tabs');
|
|
1028
1079
|
tabs.innerHTML = Array.from(channels).map(ch => {
|
|
1029
1080
|
const stats = ch === 'all'
|
|
1030
|
-
? { total: allMessages.length, mentions: allMessages.filter(m =>
|
|
1081
|
+
? { total: allMessages.length, mentions: allMessages.filter(m => hasMention(m.content)).length }
|
|
1031
1082
|
: (channelStats.get(ch) || { total: 0, mentions: 0 });
|
|
1032
1083
|
const label = ch === 'all' ? '🌐 all' : '#' + esc(ch);
|
|
1033
1084
|
const countMeta = `<span class="meta">${stats.total}</span>`;
|
|
@@ -1058,7 +1109,7 @@ function renderChat() {
|
|
|
1058
1109
|
body.innerHTML = shown.map(m => {
|
|
1059
1110
|
const agent = AGENT_INDEX.get(m.from);
|
|
1060
1111
|
const roleTag = agent ? `<span class="msg-role">${esc(agent.role)}</span>` : '';
|
|
1061
|
-
const mentioned =
|
|
1112
|
+
const mentioned = hasMention(m.content);
|
|
1062
1113
|
const channelTag = m.channel ? '<span class="msg-channel">#' + esc(m.channel) + '</span>' : '';
|
|
1063
1114
|
const editedTag = m.metadata && m.metadata.editedAt ? '<span class="msg-edited">(edited)</span>' : '';
|
|
1064
1115
|
return `
|
|
@@ -1283,9 +1334,9 @@ async function loadSharedArtifacts() {
|
|
|
1283
1334
|
const files = entries.filter(e => e && e.type === 'file');
|
|
1284
1335
|
count.textContent = files.length + ' files';
|
|
1285
1336
|
|
|
1286
|
-
// Pinned:
|
|
1337
|
+
// Pinned: operator notes (optional)
|
|
1287
1338
|
const pinned = [
|
|
1288
|
-
{ name: "
|
|
1339
|
+
{ name: "OPERATOR-NOTES.md", label: "Operator notes" },
|
|
1289
1340
|
];
|
|
1290
1341
|
|
|
1291
1342
|
const pinnedRows = pinned.map(p => {
|
|
@@ -1544,6 +1595,7 @@ async function loadReleaseStatus(force = false) {
|
|
|
1544
1595
|
badge.classList.toggle('stale', stale);
|
|
1545
1596
|
badge.classList.toggle('fresh', !stale);
|
|
1546
1597
|
badge.textContent = stale ? 'deploy: stale' : 'deploy: in sync';
|
|
1598
|
+
badge.style.display = '';
|
|
1547
1599
|
|
|
1548
1600
|
const reasons = Array.isArray(status.reasons) ? status.reasons : [];
|
|
1549
1601
|
const startupCommit = status.startup && status.startup.commit ? status.startup.commit.slice(0, 8) : 'unknown';
|
|
@@ -1579,6 +1631,7 @@ async function loadBuildInfo() {
|
|
|
1579
1631
|
badge.classList.toggle('stale', branch !== 'main');
|
|
1580
1632
|
badge.textContent = `${sha} • ${uptimeStr}`;
|
|
1581
1633
|
badge.title = `SHA: ${info.gitSha}\nBranch: ${branch}\nCommit: ${info.gitMessage}\nAuthor: ${info.gitAuthor}\nPID: ${info.pid}\nNode: ${info.nodeVersion}\nStarted: ${info.startedAt}`;
|
|
1634
|
+
badge.style.display = '';
|
|
1582
1635
|
} catch (err) {
|
|
1583
1636
|
badge.textContent = 'build: error';
|
|
1584
1637
|
badge.title = 'Failed to load build info';
|
|
@@ -1697,7 +1750,16 @@ function renderReviewQueue() {
|
|
|
1697
1750
|
});
|
|
1698
1751
|
|
|
1699
1752
|
if (validating.length === 0) {
|
|
1700
|
-
panel.style.display = '
|
|
1753
|
+
panel.style.display = '';
|
|
1754
|
+
count.textContent = '';
|
|
1755
|
+
body.innerHTML = '<div style="text-align:center;padding:24px 16px;color:var(--text-muted)">'
|
|
1756
|
+
+ '<div style="font-size:22px;margin-bottom:8px">✓</div>'
|
|
1757
|
+
+ '<div style="font-size:13px;font-weight:500;color:var(--text-bright);margin-bottom:4px">Review queue is clear</div>'
|
|
1758
|
+
+ '<div style="font-size:12px;line-height:1.5">Tasks move here when they enter <em>validating</em>. Reviewers will see SLA timers and can approve or request changes.</div>'
|
|
1759
|
+
+ '</div>';
|
|
1760
|
+
// Clear sidebar badge
|
|
1761
|
+
const navReviewBadge = document.getElementById('nav-review-count');
|
|
1762
|
+
if (navReviewBadge) navReviewBadge.textContent = '0';
|
|
1701
1763
|
return;
|
|
1702
1764
|
}
|
|
1703
1765
|
|
|
@@ -1759,7 +1821,7 @@ async function escalateReviewBreaches(breachedTasks) {
|
|
|
1759
1821
|
return '- ' + t.id + ' (' + (t.title || '').slice(0, 50) + ') — reviewer: @' + reviewer + ', waiting ' + formatDuration(t.timeInReview);
|
|
1760
1822
|
});
|
|
1761
1823
|
|
|
1762
|
-
const content = '@
|
|
1824
|
+
const content = '@owner Review SLA breach detected:\n' + lines.join('\n');
|
|
1763
1825
|
|
|
1764
1826
|
try {
|
|
1765
1827
|
await fetch(BASE + '/chat/messages', {
|
|
@@ -2738,6 +2800,11 @@ function toggleFocusMode() {
|
|
|
2738
2800
|
// Persist preference
|
|
2739
2801
|
try { localStorage.setItem('reflectt-focus-mode', focusModeActive ? '1' : '0'); } catch {}
|
|
2740
2802
|
|
|
2803
|
+
// Show/hide dev-only panels
|
|
2804
|
+
document.querySelectorAll('.panel.focus-only').forEach(panel => {
|
|
2805
|
+
panel.style.display = focusModeActive ? '' : 'none';
|
|
2806
|
+
});
|
|
2807
|
+
|
|
2741
2808
|
// Re-render kanban to add/remove QA contract details
|
|
2742
2809
|
renderKanban();
|
|
2743
2810
|
|
|
@@ -2829,9 +2896,9 @@ async function checkGettingStarted() {
|
|
|
2829
2896
|
const hasTasks = (health.tasks?.total || 0) > 0;
|
|
2830
2897
|
const hasMessages = (health.chat?.total || 0) > 0;
|
|
2831
2898
|
|
|
2832
|
-
// Step 1:
|
|
2899
|
+
// Step 1: server running — always done if dashboard loads
|
|
2833
2900
|
const step1 = document.getElementById('gs-preflight');
|
|
2834
|
-
if (step1
|
|
2901
|
+
if (step1) {
|
|
2835
2902
|
step1.classList.add('done');
|
|
2836
2903
|
step1.querySelector('.gs-icon').textContent = '✓';
|
|
2837
2904
|
}
|