@lightupai/polaris 0.0.72 → 0.0.74
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/docs/design-backfill.md +10 -0
- package/package.json +1 -1
- package/src/service/server.ts +18 -11
package/docs/design-backfill.md
CHANGED
|
@@ -156,6 +156,16 @@ Project renames, session changes, and Slack channel renames can happen during a
|
|
|
156
156
|
|
|
157
157
|
**Recommendation**: Always log `/connect` and `/disconnect` events to a separate persistent file (`~/.polaris/session-history.jsonl`) that survives daemon restarts. This gives backfill a reliable timeline of which CC session was in which project at what time.
|
|
158
158
|
|
|
159
|
+
## TODOs
|
|
160
|
+
|
|
161
|
+
- [ ] **Separate backfill recovery from Slack notifications** — Backfill currently replays events via `POST /events`, which unconditionally broadcasts to Slack. Need to separate: (1) silent recovery of events into Polaris (add a `backfill: true` flag to skip broadcasting), (2) optional catchup summaries on Slack (condensed summary of what happened during the gap, not raw event replay).
|
|
162
|
+
|
|
163
|
+
- [ ] **Control Slack reporting verbosity** — Slack notifications from Polaris events are too verbose to be usable. Need configurable verbosity levels — e.g., per-project or per-session settings to control what gets posted and at what detail level.
|
|
164
|
+
|
|
165
|
+
- [ ] **Add dedup logic to backfill with checkpointing** — Backfill currently replays all events in the time range with no dedup — the API assigns new UUIDs to each, so replaying twice doubles events. Need: (1) dedup logic so already-published events aren't re-ingested (timestamp matching or content hash per the Deduplication section above), (2) sensible checkpointing so backfill can resume without re-processing, (3) merging scheme for overlapping backfill ranges.
|
|
166
|
+
|
|
167
|
+
- [ ] **Handle session discontinuity in backfill** — Backfill currently only works against the current CC session's mapping. If a user's Polaris session disconnected, they continued working in CC, then stopped and started a new CC session (without resume), backfill from the new session can't recover events from the old one. It replays everything into the current Polaris session instead of the one that was active when the events were generated. Fix: backfill should read `session-history.jsonl` to build a timeline of CC session → Polaris session mappings, cross-reference daemon log timestamps against that timeline, and replay each event to the correct Polaris session. Both data sources persist after CC sessions end (CC transcripts in `~/.claude/projects/`, daemon logs in `~/.polaris/logs/`), so the data is available — just the routing logic is missing.
|
|
168
|
+
|
|
159
169
|
## Open Questions
|
|
160
170
|
|
|
161
171
|
1. **Should backfill be automatic?** The daemon could detect gaps on startup (compare last log entry to last API event) and auto-backfill. Risk: could replay stale events unintentionally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightupai/polaris",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.74",
|
|
4
4
|
"description": "Record and stream Claude Code sessions to Slack. Like Gong for AI coding — capture every prompt, response, and tool call. Multiplayer collaboration across AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
package/src/service/server.ts
CHANGED
|
@@ -450,25 +450,32 @@ export async function startServer(opts: {
|
|
|
450
450
|
const users = await listUsers(sql, orgId);
|
|
451
451
|
const org = await getOrgFn(sql, orgId);
|
|
452
452
|
|
|
453
|
-
// Resolve Slack user info if bot token available
|
|
453
|
+
// Resolve Slack user info if bot token available (paginate through all members)
|
|
454
454
|
let slackMembers: Array<{ id: string; name: string; display_name: string; email: string; username: string }> = [];
|
|
455
455
|
if (org?.slack_bot_token) {
|
|
456
456
|
try {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
457
|
+
type SlackMember = { id: string; name: string; real_name?: string; profile?: { display_name?: string; email?: string }; deleted?: boolean; is_bot?: boolean };
|
|
458
|
+
type SlackResponse = { ok?: boolean; members?: SlackMember[]; response_metadata?: { next_cursor?: string } };
|
|
459
|
+
let cursor = "";
|
|
460
|
+
do {
|
|
461
|
+
const url = `https://slack.com/api/users.list?limit=200${cursor ? `&cursor=${cursor}` : ""}`;
|
|
462
|
+
const slackRes = await fetch(url, {
|
|
463
|
+
headers: { Authorization: `Bearer ${org.slack_bot_token}` },
|
|
464
|
+
});
|
|
465
|
+
if (!slackRes.ok) break;
|
|
466
|
+
const slackData = (await slackRes.json()) as SlackResponse;
|
|
467
|
+
for (const m of slackData.members ?? []) {
|
|
468
|
+
if (m.deleted || m.is_bot) continue;
|
|
469
|
+
slackMembers.push({
|
|
465
470
|
id: m.id,
|
|
466
471
|
name: m.real_name ?? "",
|
|
467
472
|
display_name: m.profile?.display_name ?? "",
|
|
468
473
|
email: m.profile?.email ?? "",
|
|
469
474
|
username: m.name ?? "",
|
|
470
|
-
})
|
|
471
|
-
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
cursor = slackData.response_metadata?.next_cursor ?? "";
|
|
478
|
+
} while (cursor);
|
|
472
479
|
} catch { /* Slack API unavailable */ }
|
|
473
480
|
}
|
|
474
481
|
|