@lightupai/polaris 0.0.114 → 0.0.117
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/hooks/statusline.sh +22 -2
- package/package.json +1 -1
- package/src/client/client.ts +29 -0
- package/src/daemon/daemon.ts +73 -0
package/hooks/statusline.sh
CHANGED
|
@@ -57,8 +57,28 @@ if [ -f "$INSTALLED_PKG" ]; then
|
|
|
57
57
|
) &
|
|
58
58
|
fi
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
# Only notify when the registry version is strictly newer (prevents false
|
|
61
|
+
# positives when a freshly-updated client's cache still shows an older latest).
|
|
62
|
+
if [ -n "$CACHED_LATEST" ] && [ -n "$INSTALLED_VERSION" ]; then
|
|
63
|
+
_ver_gt() {
|
|
64
|
+
# Returns true (0) when $1 is semver-greater than $2
|
|
65
|
+
# Works for the "0.0.NNN" format used by polaris.
|
|
66
|
+
IFS=. read -r _a1 _a2 _a3 <<_EOF_
|
|
67
|
+
$1
|
|
68
|
+
_EOF_
|
|
69
|
+
IFS=. read -r _b1 _b2 _b3 <<_EOF_
|
|
70
|
+
$2
|
|
71
|
+
_EOF_
|
|
72
|
+
[ "${_a1:-0}" -gt "${_b1:-0}" ] && return 0
|
|
73
|
+
[ "${_a1:-0}" -lt "${_b1:-0}" ] && return 1
|
|
74
|
+
[ "${_a2:-0}" -gt "${_b2:-0}" ] && return 0
|
|
75
|
+
[ "${_a2:-0}" -lt "${_b2:-0}" ] && return 1
|
|
76
|
+
[ "${_a3:-0}" -gt "${_b3:-0}" ] && return 0
|
|
77
|
+
return 1
|
|
78
|
+
}
|
|
79
|
+
if _ver_gt "$CACHED_LATEST" "$INSTALLED_VERSION"; then
|
|
80
|
+
UPDATE_MSG=" [v${INSTALLED_VERSION}, update available]"
|
|
81
|
+
fi
|
|
62
82
|
fi
|
|
63
83
|
fi
|
|
64
84
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightupai/polaris",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.117",
|
|
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/client/client.ts
CHANGED
|
@@ -143,6 +143,18 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
143
143
|
properties: {},
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: "polaris_send",
|
|
148
|
+
description: "Send a message directly to a sibling session in the same project. The message is injected into that session's next prompt and logged in both sessions' Polaris timelines.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object" as const,
|
|
151
|
+
properties: {
|
|
152
|
+
session: { type: "string", description: "Target session name (e.g. 'feedback')" },
|
|
153
|
+
message: { type: "string", description: "Message to send" },
|
|
154
|
+
},
|
|
155
|
+
required: ["session", "message"],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
146
158
|
{
|
|
147
159
|
name: "polaris_reply",
|
|
148
160
|
description: "Send a message to the project floor (visible to all advisors, and to the linked Slack channel if a floor is connected).",
|
|
@@ -338,6 +350,23 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
338
350
|
}
|
|
339
351
|
}
|
|
340
352
|
|
|
353
|
+
if (name === "polaris_send") {
|
|
354
|
+
if (!currentProject) {
|
|
355
|
+
return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
|
|
356
|
+
}
|
|
357
|
+
const { session: targetSession, message } = args as { session: string; message: string };
|
|
358
|
+
try {
|
|
359
|
+
const res = await daemonPost("/send", { ccSessionId: CC_SESSION_ID, targetSession, message });
|
|
360
|
+
if (res.ok) {
|
|
361
|
+
return { content: [{ type: "text", text: `Message sent to session '${targetSession}'.` }] };
|
|
362
|
+
}
|
|
363
|
+
const body = await res.json();
|
|
364
|
+
return { content: [{ type: "text", text: `Failed to send: ${(body as { error?: string }).error ?? res.status}` }] };
|
|
365
|
+
} catch {
|
|
366
|
+
return { content: [{ type: "text", text: "Failed to reach the daemon." }] };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
341
370
|
if (name === "polaris_reply") {
|
|
342
371
|
if (!currentProject) {
|
|
343
372
|
return { content: [{ type: "text", text: "Not connected to a Polaris session. Use polaris_connect first." }] };
|
package/src/daemon/daemon.ts
CHANGED
|
@@ -1139,6 +1139,79 @@ export function startDaemon(port = Number(process.env.POLARIS_DAEMON_PORT ?? 432
|
|
|
1139
1139
|
}
|
|
1140
1140
|
}
|
|
1141
1141
|
|
|
1142
|
+
// POST /send — deliver a message to a sibling session and log it on both timelines
|
|
1143
|
+
if (method === "POST" && pathname === "/send") {
|
|
1144
|
+
try {
|
|
1145
|
+
const body = (await req.json()) as { ccSessionId: string; targetSession: string; message: string };
|
|
1146
|
+
if (!body.ccSessionId || !body.targetSession || !body.message) {
|
|
1147
|
+
return error("ccSessionId, targetSession, and message required", 400);
|
|
1148
|
+
}
|
|
1149
|
+
const sender = sessions.get(body.ccSessionId);
|
|
1150
|
+
if (!sender || !sender.project) return error("Not connected", 400);
|
|
1151
|
+
|
|
1152
|
+
// Find the target session by name within the same project
|
|
1153
|
+
const target = Array.from(sessions.values()).find(
|
|
1154
|
+
(m) => m.project === sender.project && m.session === body.targetSession && m.ccSessionId !== body.ccSessionId
|
|
1155
|
+
);
|
|
1156
|
+
if (!target) {
|
|
1157
|
+
return error(`Session '${body.targetSession}' is not connected in project '${sender.project}'`, 404);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const serviceUrl = getServiceUrlForProfile(sender.profile);
|
|
1161
|
+
const headers = await authHeadersForProfile(sender.profile);
|
|
1162
|
+
const timestamp = new Date().toISOString();
|
|
1163
|
+
|
|
1164
|
+
// 1. Log outbound event on sender's timeline
|
|
1165
|
+
await fetch(`${serviceUrl}/projects/${sender.project}/sessions/${sender.session}/events`, {
|
|
1166
|
+
method: "POST",
|
|
1167
|
+
headers,
|
|
1168
|
+
body: JSON.stringify({
|
|
1169
|
+
sender: sender.agent,
|
|
1170
|
+
payload: {
|
|
1171
|
+
hook_event_name: "SendMessage",
|
|
1172
|
+
session_id: body.ccSessionId,
|
|
1173
|
+
to: body.targetSession,
|
|
1174
|
+
message: body.message,
|
|
1175
|
+
},
|
|
1176
|
+
}),
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
// 2. Log inbound event on target's timeline (attributed to the sender's session name)
|
|
1180
|
+
await fetch(`${serviceUrl}/projects/${target.project}/sessions/${target.session}/events`, {
|
|
1181
|
+
method: "POST",
|
|
1182
|
+
headers,
|
|
1183
|
+
body: JSON.stringify({
|
|
1184
|
+
sender: sender.agent,
|
|
1185
|
+
payload: {
|
|
1186
|
+
hook_event_name: "ReceiveMessage",
|
|
1187
|
+
session_id: target.ccSessionId,
|
|
1188
|
+
from: sender.session,
|
|
1189
|
+
message: body.message,
|
|
1190
|
+
},
|
|
1191
|
+
}),
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
// 3. Inject into target's local queue for delivery on next UserPromptSubmit
|
|
1195
|
+
let queue = injectQueues.get(target.ccSessionId);
|
|
1196
|
+
if (!queue) {
|
|
1197
|
+
queue = [];
|
|
1198
|
+
injectQueues.set(target.ccSessionId, queue);
|
|
1199
|
+
}
|
|
1200
|
+
// Also register under any alias ccSessionId the target may be using
|
|
1201
|
+
for (const [id, m] of sessions) {
|
|
1202
|
+
if (m.project === target.project && m.session === target.session && !injectQueues.has(id)) {
|
|
1203
|
+
injectQueues.set(id, queue);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
queue.push({ from: sender.session, content: body.message, timestamp });
|
|
1207
|
+
|
|
1208
|
+
await logEvent("/send", body, undefined, { project: sender.project, session: sender.session, ccSessionId: body.ccSessionId });
|
|
1209
|
+
return json({ status: "delivered", from: sender.session, to: body.targetSession });
|
|
1210
|
+
} catch {
|
|
1211
|
+
return error("Invalid JSON", 400);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1142
1215
|
// GET /context/:ccSessionId/:session — proxy context fetch from cloud API
|
|
1143
1216
|
if (method === "GET" && pathname.match(/^\/context\/[^/]+\/[^/]+$/)) {
|
|
1144
1217
|
const parts = pathname.split("/");
|