@lightupai/polaris 0.0.101 → 0.0.103
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 +1 -1
- package/skills/polaris/SKILL.md +9 -5
- package/src/cli/cli.ts +20 -12
- package/src/client/client.ts +67 -3
- package/src/daemon/daemon.ts +49 -8
- package/src/service/db.ts +9 -0
- package/src/service/server.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightupai/polaris",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.103",
|
|
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/skills/polaris/SKILL.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: polaris
|
|
3
3
|
description: Connect to a Polaris multiplayer collaboration session
|
|
4
|
-
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename polaris_backfill polaris_team
|
|
5
|
-
argument-hint: [join #channel | tag [name] | backfill [duration] | rename <new-name> | disconnect | (no args for status)]
|
|
4
|
+
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename polaris_rename_session polaris_backfill polaris_team
|
|
5
|
+
argument-hint: [join #channel [session-name] | tag [name] | backfill [duration] | rename <new-name> | rename-session <name> | disconnect | (no args for status)]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Polaris — Multiplayer Collaboration
|
|
@@ -13,8 +13,8 @@ Manage your connection to a Polaris collaboration session.
|
|
|
13
13
|
|
|
14
14
|
Based on the arguments provided, do ONE of the following:
|
|
15
15
|
|
|
16
|
-
**`/polaris join #channel-name`** — Connect to an existing project, or create a new one:
|
|
17
|
-
1. Call `polaris_connect` with the given channel
|
|
16
|
+
**`/polaris join #channel-name [session-name]`** — Connect to an existing project, or create a new one:
|
|
17
|
+
1. Call `polaris_connect` with the given channel, user identity `user:manu.bansal`, and optional `session` name (e.g. `auth-feature`, `bugfix-154`)
|
|
18
18
|
2. If the response is `project_not_found`:
|
|
19
19
|
- Show the list of existing projects from the response
|
|
20
20
|
- Ask the user: "Project '#channel-name' doesn't exist. Create it? [y/N]"
|
|
@@ -22,10 +22,14 @@ Based on the arguments provided, do ONE of the following:
|
|
|
22
22
|
- If no: stop and show the existing project list so they can pick one
|
|
23
23
|
3. Report the connection status including the session name
|
|
24
24
|
|
|
25
|
-
**`/polaris rename <new-name>`** — Rename the current channel:
|
|
25
|
+
**`/polaris rename <new-name>`** — Rename the current **project** (and its Slack channel):
|
|
26
26
|
1. Call `polaris_rename` with the new name
|
|
27
27
|
2. Report the result
|
|
28
28
|
|
|
29
|
+
**`/polaris rename-session <new-name>`** — Rename this **session thread** only (not the project):
|
|
30
|
+
1. Call `polaris_rename_session` with the new name
|
|
31
|
+
2. Report the result
|
|
32
|
+
|
|
29
33
|
**`/polaris tag [name]`** — Tag a teammate on Slack:
|
|
30
34
|
1. Call `polaris_team` to get the team list with Slack identities
|
|
31
35
|
2. If a name was given, find the matching member. If no name, present a numbered list for the user to pick.
|
package/src/cli/cli.ts
CHANGED
|
@@ -428,8 +428,8 @@ async function install(participantId?: string) {
|
|
|
428
428
|
const skillContent = `---
|
|
429
429
|
name: polaris
|
|
430
430
|
description: Connect to a Polaris multiplayer collaboration session
|
|
431
|
-
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename
|
|
432
|
-
argument-hint: [join #channel | rename <new-name> | disconnect | (no args for status)]
|
|
431
|
+
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename polaris_rename_session
|
|
432
|
+
argument-hint: [join #channel [session-name] | rename <new-name> | rename-session <name> | disconnect | (no args for status)]
|
|
433
433
|
---
|
|
434
434
|
|
|
435
435
|
## Polaris — Multiplayer Collaboration
|
|
@@ -440,15 +440,19 @@ Manage your connection to a Polaris collaboration session.
|
|
|
440
440
|
|
|
441
441
|
Based on the arguments provided, do ONE of the following:
|
|
442
442
|
|
|
443
|
-
**\`/polaris join #channel-name\`** — Connect to a channel:
|
|
444
|
-
1. Call \`polaris_connect\` with the given project
|
|
445
|
-
2. A session name is auto-generated
|
|
443
|
+
**\`/polaris join #channel-name [session-name]\`** — Connect to a channel:
|
|
444
|
+
1. Call \`polaris_connect\` with the given project, user identity ${identity}, and optional \`session\` name (e.g. \`auth-feature\`, \`bugfix-154\`)
|
|
445
|
+
2. A session name is auto-generated if not provided
|
|
446
446
|
3. Report the connection status including the session name
|
|
447
447
|
|
|
448
|
-
**\`/polaris rename <new-name>\`** — Rename the current project:
|
|
448
|
+
**\`/polaris rename <new-name>\`** — Rename the current **project** (and its Slack channel):
|
|
449
449
|
1. Call \`polaris_rename\` with the new name
|
|
450
450
|
2. Report the result
|
|
451
451
|
|
|
452
|
+
**\`/polaris rename-session <new-name>\`** — Rename this **session thread** only (not the project):
|
|
453
|
+
1. Call \`polaris_rename_session\` with the new name
|
|
454
|
+
2. Report the result
|
|
455
|
+
|
|
452
456
|
**\`/polaris disconnect\`** — Disconnect:
|
|
453
457
|
1. Call \`polaris_disconnect\`
|
|
454
458
|
2. Confirm disconnection
|
|
@@ -567,8 +571,8 @@ async function login(appUrl: string, profileName?: string, loginHint?: string) {
|
|
|
567
571
|
const skillContent = `---
|
|
568
572
|
name: polaris
|
|
569
573
|
description: Connect to a Polaris multiplayer collaboration session
|
|
570
|
-
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename
|
|
571
|
-
argument-hint: [join #channel | rename <new-name> | disconnect | (no args for status)]
|
|
574
|
+
allowed-tools: polaris_connect polaris_disconnect polaris_status polaris_reply polaris_context polaris_rename polaris_rename_session
|
|
575
|
+
argument-hint: [join #channel [session-name] | rename <new-name> | rename-session <name> | disconnect | (no args for status)]
|
|
572
576
|
---
|
|
573
577
|
|
|
574
578
|
## Polaris — Multiplayer Collaboration
|
|
@@ -579,15 +583,19 @@ Manage your connection to a Polaris collaboration session.
|
|
|
579
583
|
|
|
580
584
|
Based on the arguments provided, do ONE of the following:
|
|
581
585
|
|
|
582
|
-
**\`/polaris join #channel-name\`** — Connect to a channel:
|
|
583
|
-
1. Call \`polaris_connect\` with the given project
|
|
584
|
-
2. A session name is auto-generated
|
|
586
|
+
**\`/polaris join #channel-name [session-name]\`** — Connect to a channel:
|
|
587
|
+
1. Call \`polaris_connect\` with the given project, user identity ${identity}, and optional \`session\` name (e.g. \`auth-feature\`, \`bugfix-154\`)
|
|
588
|
+
2. A session name is auto-generated if not provided
|
|
585
589
|
3. Report the connection status including the session name
|
|
586
590
|
|
|
587
|
-
**\`/polaris rename <new-name>\`** — Rename the current project:
|
|
591
|
+
**\`/polaris rename <new-name>\`** — Rename the current **project** (and its Slack channel):
|
|
588
592
|
1. Call \`polaris_rename\` with the new name
|
|
589
593
|
2. Report the result
|
|
590
594
|
|
|
595
|
+
**\`/polaris rename-session <new-name>\`** — Rename this **session thread** only (not the project):
|
|
596
|
+
1. Call \`polaris_rename_session\` with the new name
|
|
597
|
+
2. Report the result
|
|
598
|
+
|
|
591
599
|
**\`/polaris disconnect\`** — Disconnect:
|
|
592
600
|
1. Call \`polaris_disconnect\`
|
|
593
601
|
2. Confirm disconnection
|
package/src/client/client.ts
CHANGED
|
@@ -156,7 +156,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
name: "polaris_rename",
|
|
159
|
-
description: "Rename the current
|
|
159
|
+
description: "Rename the current PROJECT (and its linked Slack channel). To rename just this session thread, use polaris_rename_session instead.",
|
|
160
160
|
inputSchema: {
|
|
161
161
|
type: "object" as const,
|
|
162
162
|
properties: {
|
|
@@ -165,6 +165,17 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
165
165
|
required: ["name"],
|
|
166
166
|
},
|
|
167
167
|
},
|
|
168
|
+
{
|
|
169
|
+
name: "polaris_rename_session",
|
|
170
|
+
description: "Rename this session thread (e.g. from 's-3f95' to 'auth-feature'). Does not affect the project or other sessions.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object" as const,
|
|
173
|
+
properties: {
|
|
174
|
+
name: { type: "string", description: "New session name (e.g. 'auth-feature', 'bugfix-154')" },
|
|
175
|
+
},
|
|
176
|
+
required: ["name"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
168
179
|
{
|
|
169
180
|
name: "polaris_context",
|
|
170
181
|
description: "Fetch activity from a sibling session in this project. Use this to see what other drivers have been doing.",
|
|
@@ -192,6 +203,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
192
203
|
properties: {
|
|
193
204
|
duration: { type: "string", description: "Time range to backfill (e.g., '2h', '30m', '1d'). Auto-detects if omitted." },
|
|
194
205
|
from: { type: "string", description: "ISO timestamp to backfill from. Overrides duration." },
|
|
206
|
+
dry_run: { type: "boolean", description: "If true, report how many events would be sent without actually sending them." },
|
|
195
207
|
},
|
|
196
208
|
},
|
|
197
209
|
},
|
|
@@ -339,6 +351,26 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
339
351
|
}
|
|
340
352
|
}
|
|
341
353
|
|
|
354
|
+
if (name === "polaris_rename_session") {
|
|
355
|
+
if (!currentProject) {
|
|
356
|
+
return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
|
|
357
|
+
}
|
|
358
|
+
const newName = (args as { name: string }).name;
|
|
359
|
+
try {
|
|
360
|
+
const res = await daemonPost("/rename-session", { ccSessionId: CC_SESSION_ID, newName });
|
|
361
|
+
const body = await res.json() as { status?: string; oldName?: string; newName?: string; error?: string };
|
|
362
|
+
if (res.ok) {
|
|
363
|
+
const oldName = currentSession;
|
|
364
|
+
currentSession = newName;
|
|
365
|
+
await saveSessionState(currentProject, currentSession, currentUser, currentProfile);
|
|
366
|
+
return { content: [{ type: "text", text: `Renamed session "${oldName}" to "${newName}".` }] };
|
|
367
|
+
}
|
|
368
|
+
return { content: [{ type: "text", text: `Failed to rename session: ${body.error ?? "unknown error"}` }] };
|
|
369
|
+
} catch {
|
|
370
|
+
return { content: [{ type: "text", text: "Failed to rename session — is the Polaris daemon running?" }] };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
342
374
|
if (name === "polaris_context") {
|
|
343
375
|
if (!currentProject) {
|
|
344
376
|
return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
|
|
@@ -397,16 +429,20 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
397
429
|
if (!currentProject) {
|
|
398
430
|
return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
|
|
399
431
|
}
|
|
400
|
-
const { duration, from } = (args ?? {}) as { duration?: string; from?: string };
|
|
432
|
+
const { duration, from, dry_run } = (args ?? {}) as { duration?: string; from?: string; dry_run?: boolean };
|
|
401
433
|
try {
|
|
402
434
|
const res = await daemonPost("/backfill", {
|
|
403
435
|
ccSessionId: CC_SESSION_ID,
|
|
404
436
|
...(duration ? { duration } : {}),
|
|
405
437
|
...(from ? { from } : {}),
|
|
438
|
+
...(dry_run ? { dry_run: true } : {}),
|
|
406
439
|
});
|
|
407
|
-
const body = await res.json() as { recovered: number; source: string; gaps: string[] };
|
|
440
|
+
const body = await res.json() as { recovered: number; would_recover?: number; source: string; gaps: string[] };
|
|
408
441
|
if (res.ok) {
|
|
409
442
|
const gapInfo = body.gaps.length > 0 ? `\nGaps: ${body.gaps.join(", ")}` : "";
|
|
443
|
+
if (dry_run) {
|
|
444
|
+
return { content: [{ type: "text", text: `Dry run: ${body.would_recover ?? 0} events in daemon log would be sent.${gapInfo}` }] };
|
|
445
|
+
}
|
|
410
446
|
return { content: [{ type: "text", text: `Backfill complete: ${body.recovered} events recovered from ${body.source}.${gapInfo}` }] };
|
|
411
447
|
}
|
|
412
448
|
return { content: [{ type: "text", text: `Backfill failed: ${(body as unknown as { error?: string }).error ?? "unknown error"}` }] };
|
|
@@ -460,6 +496,34 @@ async function main() {
|
|
|
460
496
|
}
|
|
461
497
|
}
|
|
462
498
|
|
|
499
|
+
// Heartbeat: check every 60s whether the daemon still has our session mapped.
|
|
500
|
+
// If not (dropped WS, daemon restart, compaction), silently re-connect so
|
|
501
|
+
// activity reaches the channel without a manual /polaris join.
|
|
502
|
+
setInterval(async () => {
|
|
503
|
+
if (!currentProject) return; // not connected — nothing to heartbeat
|
|
504
|
+
try {
|
|
505
|
+
const res = await daemonGet(`/status/${CC_SESSION_ID}`);
|
|
506
|
+
const body = await res.json() as { connected?: boolean; project?: string; session?: string };
|
|
507
|
+
if (body.connected) return; // all good
|
|
508
|
+
// Daemon lost the mapping — attempt to reconnect to the same session
|
|
509
|
+
const reconnectRes = await daemonPost("/connect", {
|
|
510
|
+
ccSessionId: CC_SESSION_ID,
|
|
511
|
+
project: currentProject,
|
|
512
|
+
session: currentSession,
|
|
513
|
+
user: currentUser,
|
|
514
|
+
profile: currentProfile,
|
|
515
|
+
});
|
|
516
|
+
if (reconnectRes.ok) {
|
|
517
|
+
console.error(`Polaris: session dropped; auto-reconnected to #${currentProject}/${currentSession}`);
|
|
518
|
+
} else {
|
|
519
|
+
console.error(`Polaris: session dropped and reconnect failed. Run /polaris to check status.`);
|
|
520
|
+
currentProject = "";
|
|
521
|
+
currentSession = "";
|
|
522
|
+
currentUser = "";
|
|
523
|
+
}
|
|
524
|
+
} catch { /* daemon unreachable — will retry next heartbeat */ }
|
|
525
|
+
}, 60_000);
|
|
526
|
+
|
|
463
527
|
// inject delivery is handled via the UserPromptSubmit hook (see daemon injectQueues); claude/channel push deferred
|
|
464
528
|
|
|
465
529
|
const transport = new StdioServerTransport();
|
package/src/daemon/daemon.ts
CHANGED
|
@@ -15,6 +15,7 @@ interface SessionMapping {
|
|
|
15
15
|
slackChannel?: string;
|
|
16
16
|
ws: WebSocket | null;
|
|
17
17
|
pendingMapping?: boolean; // true until the first hook event from this shell claims the mapping
|
|
18
|
+
closingIntentionally?: boolean; // set before deliberate ws.close() so onclose can distinguish drops
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function generateSessionName(): string {
|
|
@@ -164,14 +165,18 @@ function connectCloudWs(mapping: SessionMapping) {
|
|
|
164
165
|
};
|
|
165
166
|
|
|
166
167
|
ws.onclose = () => {
|
|
167
|
-
|
|
168
|
-
if (
|
|
168
|
+
const m = sessions.get(mapping.ccSessionId);
|
|
169
|
+
if (m && m.project === mapping.project && mapping.project !== "" && !m.closingIntentionally) {
|
|
170
|
+
// Unexpected cloud WS drop — log it so the user has a record
|
|
171
|
+
void logSessionChange("ws-drop", mapping.ccSessionId, mapping.project, mapping.session, mapping.user, mapping.agent);
|
|
169
172
|
setTimeout(() => {
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
const current = sessions.get(mapping.ccSessionId);
|
|
174
|
+
if (current && current.project) {
|
|
175
|
+
connectCloudWs(current);
|
|
172
176
|
}
|
|
173
177
|
}, 3000);
|
|
174
178
|
}
|
|
179
|
+
if (m) m.closingIntentionally = false;
|
|
175
180
|
};
|
|
176
181
|
|
|
177
182
|
ws.onerror = () => {
|
|
@@ -184,6 +189,7 @@ function connectCloudWs(mapping: SessionMapping) {
|
|
|
184
189
|
function disconnectCloudWs(ccSessionId: string) {
|
|
185
190
|
const mapping = sessions.get(ccSessionId);
|
|
186
191
|
if (mapping?.ws) {
|
|
192
|
+
mapping.closingIntentionally = true;
|
|
187
193
|
mapping.ws.close();
|
|
188
194
|
mapping.ws = null;
|
|
189
195
|
}
|
|
@@ -310,7 +316,7 @@ async function scanOutbox(): Promise<void> {
|
|
|
310
316
|
|
|
311
317
|
const SESSION_HISTORY_FILE = join(homedir(), ".polaris", "session-history.jsonl");
|
|
312
318
|
|
|
313
|
-
async function logSessionChange(action: "connect" | "disconnect", ccSessionId: string, project: string, session: string, user: string, agent: string): Promise<void> {
|
|
319
|
+
async function logSessionChange(action: "connect" | "disconnect" | "ws-drop", ccSessionId: string, project: string, session: string, user: string, agent: string): Promise<void> {
|
|
314
320
|
try {
|
|
315
321
|
await ensureLogDir();
|
|
316
322
|
const entry = JSON.stringify({ t: new Date().toISOString(), action, ccSessionId, project, session, user, agent });
|
|
@@ -320,7 +326,7 @@ async function logSessionChange(action: "connect" | "disconnect", ccSessionId: s
|
|
|
320
326
|
|
|
321
327
|
// --- Backfill: recover lost events from daemon log ---
|
|
322
328
|
|
|
323
|
-
async function backfill(mapping: SessionMapping, duration?: string, from?: string): Promise<{ recovered: number; source: string; gaps: string[] }> {
|
|
329
|
+
async function backfill(mapping: SessionMapping, duration?: string, from?: string, dryRun = false): Promise<{ recovered: number; would_recover?: number; source: string; gaps: string[] }> {
|
|
324
330
|
const now = new Date();
|
|
325
331
|
let since: Date;
|
|
326
332
|
|
|
@@ -389,6 +395,11 @@ async function backfill(mapping: SessionMapping, duration?: string, from?: strin
|
|
|
389
395
|
return { recovered: 0, source: "daemon_log", gaps: [`no log entries found since ${since.toISOString()}`] };
|
|
390
396
|
}
|
|
391
397
|
|
|
398
|
+
// Dry-run: report how many events would be sent without actually sending
|
|
399
|
+
if (dryRun) {
|
|
400
|
+
return { recovered: 0, would_recover: logEntries.length, source: "daemon_log", gaps: [] };
|
|
401
|
+
}
|
|
402
|
+
|
|
392
403
|
// Replay events to the API
|
|
393
404
|
let recovered = 0;
|
|
394
405
|
const serviceUrl = getServiceUrlForProfile(mapping.profile);
|
|
@@ -766,6 +777,36 @@ export function startDaemon(port = Number(process.env.POLARIS_DAEMON_PORT ?? 432
|
|
|
766
777
|
}
|
|
767
778
|
}
|
|
768
779
|
|
|
780
|
+
// POST /rename-session — rename the current session (not the project)
|
|
781
|
+
if (method === "POST" && pathname === "/rename-session") {
|
|
782
|
+
try {
|
|
783
|
+
const body = (await req.json()) as { ccSessionId: string; newName: string };
|
|
784
|
+
if (!body.ccSessionId || !body.newName) return error("ccSessionId and newName required", 400);
|
|
785
|
+
const mapping = sessions.get(body.ccSessionId);
|
|
786
|
+
if (!mapping || !mapping.project) return error("Not connected", 400);
|
|
787
|
+
|
|
788
|
+
const serviceUrl = getServiceUrlForProfile(mapping.profile);
|
|
789
|
+
const res = await fetch(
|
|
790
|
+
`${serviceUrl}/projects/${mapping.project}/sessions/${mapping.session}/rename`,
|
|
791
|
+
{
|
|
792
|
+
method: "POST",
|
|
793
|
+
headers: await authHeadersForProfile(mapping.profile),
|
|
794
|
+
body: JSON.stringify({ name: body.newName }),
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
if (!res.ok) {
|
|
798
|
+
const err = await res.text();
|
|
799
|
+
return new Response(err, { status: res.status });
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const oldName = mapping.session;
|
|
803
|
+
mapping.session = body.newName;
|
|
804
|
+
return json({ status: "renamed", oldName, newName: body.newName });
|
|
805
|
+
} catch {
|
|
806
|
+
return error("Invalid JSON", 400);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
769
810
|
// POST /channel-update — bridge pushes channel rename notifications
|
|
770
811
|
if (method === "POST" && pathname === "/channel-update") {
|
|
771
812
|
try {
|
|
@@ -929,12 +970,12 @@ export function startDaemon(port = Number(process.env.POLARIS_DAEMON_PORT ?? 432
|
|
|
929
970
|
// POST /backfill — recover lost events from daemon log
|
|
930
971
|
if (method === "POST" && pathname === "/backfill") {
|
|
931
972
|
try {
|
|
932
|
-
const body = (await req.json()) as { ccSessionId: string; duration?: string; from?: string };
|
|
973
|
+
const body = (await req.json()) as { ccSessionId: string; duration?: string; from?: string; dry_run?: boolean };
|
|
933
974
|
if (!body.ccSessionId) return error("ccSessionId required", 400);
|
|
934
975
|
const mapping = sessions.get(body.ccSessionId);
|
|
935
976
|
if (!mapping || !mapping.project) return error("Not connected", 400);
|
|
936
977
|
|
|
937
|
-
const result = await backfill(mapping, body.duration, body.from);
|
|
978
|
+
const result = await backfill(mapping, body.duration, body.from, body.dry_run ?? false);
|
|
938
979
|
return json(result);
|
|
939
980
|
} catch {
|
|
940
981
|
return error("Invalid JSON", 400);
|
package/src/service/db.ts
CHANGED
|
@@ -311,6 +311,15 @@ export async function createProject(sql: Sql, orgId: string, name: string): Prom
|
|
|
311
311
|
return rowToProject(row);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
export async function renameSession(sql: Sql, orgId: string, project: string, oldName: string, newName: string): Promise<void> {
|
|
315
|
+
const result = await sql`
|
|
316
|
+
UPDATE sessions SET name = ${newName}
|
|
317
|
+
WHERE org_id = ${orgId} AND name = ${oldName}
|
|
318
|
+
AND project_id = (SELECT id FROM projects WHERE org_id = ${orgId} AND name = ${project})
|
|
319
|
+
`;
|
|
320
|
+
if (result.count === 0) throw new Error(`Session not found: ${project}/${oldName}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
314
323
|
export async function renameProject(sql: Sql, orgId: string, oldName: string, newName: string): Promise<void> {
|
|
315
324
|
await sql`
|
|
316
325
|
UPDATE projects SET name = ${newName}, slack_channel_name = ${newName}
|
package/src/service/server.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
listProjects,
|
|
10
10
|
getProject,
|
|
11
11
|
renameProject,
|
|
12
|
+
renameSession,
|
|
12
13
|
createSession,
|
|
13
14
|
getSession,
|
|
14
15
|
setDriver,
|
|
@@ -378,6 +379,18 @@ export async function startServer(opts: {
|
|
|
378
379
|
return json({ status: "renamed", oldName: params.proj, newName: body.name });
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
params = matchRoute(method, pathname, "/projects/:proj/sessions/:sess/rename", "POST");
|
|
383
|
+
if (params) {
|
|
384
|
+
const body = await req.json() as { name?: string };
|
|
385
|
+
if (!body.name) return error("name is required", 400);
|
|
386
|
+
try {
|
|
387
|
+
await renameSession(sql, orgId, params.proj, params.sess, body.name);
|
|
388
|
+
} catch (e) {
|
|
389
|
+
return error((e as Error).message, 404);
|
|
390
|
+
}
|
|
391
|
+
return json({ status: "renamed", oldName: params.sess, newName: body.name });
|
|
392
|
+
}
|
|
393
|
+
|
|
381
394
|
// --- Project ACL endpoints (visibility & members) ---
|
|
382
395
|
|
|
383
396
|
params = matchRoute(method, pathname, "/projects/:proj/visibility", "POST");
|