@cfio/cohort-sync 0.2.2 → 0.2.4
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/dist/index.js +181 -38
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11506,41 +11506,59 @@ var _systemSchema = defineSchema({
|
|
|
11506
11506
|
function deriveConvexUrl(apiUrl) {
|
|
11507
11507
|
return apiUrl.replace(/\.convex\.site\/?$/, ".convex.cloud");
|
|
11508
11508
|
}
|
|
11509
|
+
var DEFAULT_BEHAVIORAL_PROMPT = `BEFORE RESPONDING:
|
|
11510
|
+
- Does your planned response address the task's stated scope? If not, do not comment.
|
|
11511
|
+
- Do not post acknowledgment-only responses ("got it", "sounds good", "confirmed"). If you have no new information to add, do not comment.
|
|
11512
|
+
- If the work is complete, transition the task to "waiting" and set noReply=true on your final comment, then stop working on this task.`;
|
|
11509
11513
|
function buildNotificationMessage(n) {
|
|
11510
11514
|
let header;
|
|
11511
11515
|
let cta;
|
|
11512
11516
|
switch (n.type) {
|
|
11513
11517
|
case "comment":
|
|
11514
11518
|
if (n.isMentioned) {
|
|
11515
|
-
header =
|
|
11519
|
+
header = `You were @mentioned on task #${n.taskNumber} "${n.taskTitle}"
|
|
11516
11520
|
By: ${n.actorName}`;
|
|
11517
11521
|
cta = "You were directly mentioned. Read the comment and respond using the cohort_comment tool.";
|
|
11518
11522
|
} else {
|
|
11519
|
-
header =
|
|
11523
|
+
header = `New comment on task #${n.taskNumber} "${n.taskTitle}"
|
|
11520
11524
|
From: ${n.actorName}`;
|
|
11521
11525
|
cta = "Read the comment thread and respond using the cohort_comment tool if appropriate.";
|
|
11522
11526
|
}
|
|
11523
11527
|
break;
|
|
11524
11528
|
case "assignment":
|
|
11525
|
-
header =
|
|
11529
|
+
header = `You were assigned to task #${n.taskNumber} "${n.taskTitle}"
|
|
11526
11530
|
By: ${n.actorName}`;
|
|
11527
11531
|
cta = "Review the task description and begin working on it.";
|
|
11528
11532
|
break;
|
|
11529
11533
|
case "status_change":
|
|
11530
|
-
header =
|
|
11534
|
+
header = `Status changed on task #${n.taskNumber} "${n.taskTitle}"
|
|
11531
11535
|
By: ${n.actorName}`;
|
|
11532
11536
|
cta = "Review the status change and take any follow-up action needed.";
|
|
11533
11537
|
break;
|
|
11534
11538
|
default:
|
|
11535
|
-
header =
|
|
11539
|
+
header = `Notification on task #${n.taskNumber} "${n.taskTitle}"
|
|
11536
11540
|
From: ${n.actorName}`;
|
|
11537
11541
|
cta = "Check the task and respond if needed.";
|
|
11538
11542
|
}
|
|
11539
11543
|
const body = n.preview ? `
|
|
11540
11544
|
Comment: "${n.preview}"` : "";
|
|
11541
|
-
|
|
11545
|
+
let scope = "";
|
|
11546
|
+
if (n.taskDescription && n.type === "comment") {
|
|
11547
|
+
const truncated = n.taskDescription.length > 500 ? n.taskDescription.slice(0, 500) + "..." : n.taskDescription;
|
|
11548
|
+
scope = `
|
|
11542
11549
|
|
|
11543
|
-
${
|
|
11550
|
+
Scope: ${truncated}`;
|
|
11551
|
+
}
|
|
11552
|
+
const prompt = n.behavioralPrompt ? `${n.behavioralPrompt}
|
|
11553
|
+
|
|
11554
|
+
${DEFAULT_BEHAVIORAL_PROMPT}` : DEFAULT_BEHAVIORAL_PROMPT;
|
|
11555
|
+
const promptBlock = n.type === "comment" ? `
|
|
11556
|
+
|
|
11557
|
+
---
|
|
11558
|
+
${prompt}` : "";
|
|
11559
|
+
return `${header}${scope}${body}
|
|
11560
|
+
|
|
11561
|
+
${cta}${promptBlock}`;
|
|
11544
11562
|
}
|
|
11545
11563
|
async function injectNotification(port, hooksToken, n, agentId = "main") {
|
|
11546
11564
|
const response = await fetch(`http://localhost:${port}/hooks/agent`, {
|
|
@@ -11566,6 +11584,9 @@ var markDeliveredByPlugin = makeFunctionReference("notifications:markDeliveredBy
|
|
|
11566
11584
|
var upsertTelemetryFromPlugin = makeFunctionReference("telemetryPlugin:upsertTelemetryFromPlugin");
|
|
11567
11585
|
var upsertSessionsFromPlugin = makeFunctionReference("telemetryPlugin:upsertSessionsFromPlugin");
|
|
11568
11586
|
var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivityFromPlugin");
|
|
11587
|
+
var getPendingCommandsForPlugin = makeFunctionReference("gatewayCommands:getPendingForPlugin");
|
|
11588
|
+
var acknowledgeCommandRef = makeFunctionReference("gatewayCommands:acknowledgeCommand");
|
|
11589
|
+
var addCommentFromPluginRef = makeFunctionReference("comments:addCommentFromPlugin");
|
|
11569
11590
|
var HOT_KEY = "__cohort_sync__";
|
|
11570
11591
|
function getHotState() {
|
|
11571
11592
|
let state = globalThis[HOT_KEY];
|
|
@@ -11705,6 +11726,61 @@ async function initSubscription(port, cfg, hooksToken, logger) {
|
|
|
11705
11726
|
}
|
|
11706
11727
|
state.unsubscribers = [...unsubscribers];
|
|
11707
11728
|
}
|
|
11729
|
+
function initCommandSubscription(cfg, logger) {
|
|
11730
|
+
const c = getOrCreateClient();
|
|
11731
|
+
if (!c) {
|
|
11732
|
+
logger.warn("cohort-sync: no ConvexClient \u2014 command subscription skipped");
|
|
11733
|
+
return;
|
|
11734
|
+
}
|
|
11735
|
+
let processing = false;
|
|
11736
|
+
const unsubscribe = c.onUpdate(
|
|
11737
|
+
getPendingCommandsForPlugin,
|
|
11738
|
+
{ apiKey: cfg.apiKey },
|
|
11739
|
+
async (commands) => {
|
|
11740
|
+
if (processing) return;
|
|
11741
|
+
if (commands.length === 0) return;
|
|
11742
|
+
processing = true;
|
|
11743
|
+
const cmd = commands[commands.length - 1];
|
|
11744
|
+
logger.info(`cohort-sync: received gateway command: ${cmd.type} (${cmd._id})`);
|
|
11745
|
+
if (cmd.type === "restart") {
|
|
11746
|
+
try {
|
|
11747
|
+
await c.mutation(acknowledgeCommandRef, {
|
|
11748
|
+
commandId: cmd._id,
|
|
11749
|
+
apiKey: cfg.apiKey
|
|
11750
|
+
});
|
|
11751
|
+
logger.info("cohort-sync: restart command acknowledged, terminating in 500ms");
|
|
11752
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
11753
|
+
process.kill(process.pid, "SIGTERM");
|
|
11754
|
+
} catch (err) {
|
|
11755
|
+
logger.error(`cohort-sync: failed to process restart command: ${String(err)}`);
|
|
11756
|
+
processing = false;
|
|
11757
|
+
}
|
|
11758
|
+
} else {
|
|
11759
|
+
logger.warn(`cohort-sync: unknown command type: ${cmd.type}`);
|
|
11760
|
+
processing = false;
|
|
11761
|
+
}
|
|
11762
|
+
},
|
|
11763
|
+
(err) => {
|
|
11764
|
+
logger.error(`cohort-sync: command subscription error: ${String(err)}`);
|
|
11765
|
+
}
|
|
11766
|
+
);
|
|
11767
|
+
unsubscribers.push(unsubscribe);
|
|
11768
|
+
getHotState().unsubscribers = [...unsubscribers];
|
|
11769
|
+
logger.info("cohort-sync: command subscription active");
|
|
11770
|
+
}
|
|
11771
|
+
async function callAddCommentFromPlugin(apiKey, args) {
|
|
11772
|
+
const c = getOrCreateClient();
|
|
11773
|
+
if (!c) {
|
|
11774
|
+
throw new Error("Convex client not initialized \u2014 subscription may not be active");
|
|
11775
|
+
}
|
|
11776
|
+
return await c.mutation(addCommentFromPluginRef, {
|
|
11777
|
+
apiKey,
|
|
11778
|
+
taskNumber: args.taskNumber,
|
|
11779
|
+
agentName: args.agentName,
|
|
11780
|
+
content: args.content,
|
|
11781
|
+
noReply: args.noReply
|
|
11782
|
+
});
|
|
11783
|
+
}
|
|
11708
11784
|
function closeSubscription() {
|
|
11709
11785
|
for (const unsub of unsubscribers) {
|
|
11710
11786
|
try {
|
|
@@ -12731,31 +12807,75 @@ function registerHooks(api, cfg) {
|
|
|
12731
12807
|
description: "Post a comment on a Cohort task. Use this to respond to @mentions or collaborate on tasks.",
|
|
12732
12808
|
parameters: Type.Object({
|
|
12733
12809
|
task_number: Type.Number({ description: "Task number (e.g. 312)" }),
|
|
12734
|
-
comment: Type.String({ description: "Comment text to post" })
|
|
12810
|
+
comment: Type.String({ description: "Comment text to post" }),
|
|
12811
|
+
no_reply: Type.Optional(Type.Boolean({
|
|
12812
|
+
description: "If true, no notifications will be sent for this comment. Use for final/closing comments."
|
|
12813
|
+
}))
|
|
12735
12814
|
}),
|
|
12736
12815
|
async execute(_toolCallId, params) {
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12816
|
+
try {
|
|
12817
|
+
const result = await callAddCommentFromPlugin(cfg.apiKey, {
|
|
12818
|
+
taskNumber: params.task_number,
|
|
12819
|
+
agentName,
|
|
12820
|
+
content: params.comment,
|
|
12821
|
+
noReply: params.no_reply ?? false
|
|
12822
|
+
});
|
|
12823
|
+
const lines = [`Comment posted on task #${params.task_number}.`];
|
|
12824
|
+
if (result.stats) {
|
|
12825
|
+
lines.push("");
|
|
12826
|
+
lines.push(`This task has ${result.stats.totalComments} comments. ${result.stats.myRecentCount}/${result.stats.threshold} hourly limit used on this task.`);
|
|
12748
12827
|
}
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12828
|
+
if (result.budget) {
|
|
12829
|
+
lines.push(`Daily budget: ${result.budget.used}/${result.budget.limit}`);
|
|
12830
|
+
}
|
|
12831
|
+
return {
|
|
12832
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
12833
|
+
details: result
|
|
12834
|
+
};
|
|
12835
|
+
} catch (err) {
|
|
12836
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12837
|
+
if (msg.includes("AGENT_COMMENTS_LOCKED")) {
|
|
12838
|
+
return {
|
|
12839
|
+
content: [{
|
|
12840
|
+
type: "text",
|
|
12841
|
+
text: `Cannot comment on task #${params.task_number}.
|
|
12842
|
+
Reason: Agent comments are locked on this task.
|
|
12843
|
+
Do not re-attempt to comment on this task.`
|
|
12844
|
+
}],
|
|
12845
|
+
details: { error: "AGENT_COMMENTS_LOCKED", taskNumber: params.task_number }
|
|
12846
|
+
};
|
|
12847
|
+
}
|
|
12848
|
+
if (msg.includes("TASK_HOUR_LIMIT_REACHED")) {
|
|
12849
|
+
const parts = msg.split("|");
|
|
12850
|
+
const count = parts[1] ?? "?";
|
|
12851
|
+
const limit = parts[2] ?? "?";
|
|
12852
|
+
return {
|
|
12853
|
+
content: [{
|
|
12854
|
+
type: "text",
|
|
12855
|
+
text: `Cannot comment on task #${params.task_number}.
|
|
12856
|
+
Reason: You have posted ${count} comments on this task in the last hour (limit: ${limit}).
|
|
12857
|
+
Step back from this task. Do not comment again until the next hour.`
|
|
12858
|
+
}],
|
|
12859
|
+
details: { error: "TASK_HOUR_LIMIT_REACHED", count, limit, taskNumber: params.task_number }
|
|
12860
|
+
};
|
|
12861
|
+
}
|
|
12862
|
+
if (msg.includes("DAILY_LIMIT_REACHED")) {
|
|
12863
|
+
const parts = msg.split("|");
|
|
12864
|
+
const used = parts[1] ?? "?";
|
|
12865
|
+
const max = parts[2] ?? "?";
|
|
12866
|
+
const resetAt = parts[3] ?? "tomorrow";
|
|
12867
|
+
return {
|
|
12868
|
+
content: [{
|
|
12869
|
+
type: "text",
|
|
12870
|
+
text: `Cannot comment on task #${params.task_number}.
|
|
12871
|
+
Reason: Daily comment limit reached (${used}/${max}).
|
|
12872
|
+
Do not attempt to make more comments until ${resetAt}.`
|
|
12873
|
+
}],
|
|
12874
|
+
details: { error: "DAILY_LIMIT_REACHED", used, max, resetAt, taskNumber: params.task_number }
|
|
12875
|
+
};
|
|
12876
|
+
}
|
|
12877
|
+
throw err;
|
|
12753
12878
|
}
|
|
12754
|
-
const result = await res.json();
|
|
12755
|
-
return {
|
|
12756
|
-
content: [{ type: "text", text: `Comment posted on task #${params.task_number}` }],
|
|
12757
|
-
details: result
|
|
12758
|
-
};
|
|
12759
12879
|
}
|
|
12760
12880
|
};
|
|
12761
12881
|
});
|
|
@@ -12766,6 +12886,18 @@ function registerHooks(api, cfg) {
|
|
|
12766
12886
|
if (m && typeof m === "object" && "primary" in m) return String(m.primary);
|
|
12767
12887
|
return "unknown";
|
|
12768
12888
|
}
|
|
12889
|
+
function getModelContextLimit(model) {
|
|
12890
|
+
const m = model.toLowerCase();
|
|
12891
|
+
if (m.includes("opus") || m.includes("sonnet") || m.includes("haiku")) return 2e5;
|
|
12892
|
+
if (m.includes("gpt-4o")) return 128e3;
|
|
12893
|
+
if (m.includes("gpt-4-turbo") || m.includes("gpt-4-1")) return 128e3;
|
|
12894
|
+
if (m.includes("gpt-4")) return 8192;
|
|
12895
|
+
if (m.includes("o3") || m.includes("o4-mini")) return 2e5;
|
|
12896
|
+
if (m.includes("gemini-2")) return 1e6;
|
|
12897
|
+
if (m.includes("gemini")) return 128e3;
|
|
12898
|
+
if (m.includes("deepseek")) return 128e3;
|
|
12899
|
+
return 2e5;
|
|
12900
|
+
}
|
|
12769
12901
|
async function pushHeartbeat() {
|
|
12770
12902
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12771
12903
|
for (const agentId of allAgentIds) {
|
|
@@ -12846,6 +12978,7 @@ function registerHooks(api, cfg) {
|
|
|
12846
12978
|
).catch((err) => {
|
|
12847
12979
|
logger.error(`cohort-sync: subscription init failed: ${String(err)}`);
|
|
12848
12980
|
});
|
|
12981
|
+
initCommandSubscription(cfg, logger);
|
|
12849
12982
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12850
12983
|
for (const agentId of allAgentIds) {
|
|
12851
12984
|
const agentName = resolveAgentName(agentId);
|
|
@@ -12891,19 +13024,30 @@ function registerHooks(api, cfg) {
|
|
|
12891
13024
|
}
|
|
12892
13025
|
});
|
|
12893
13026
|
api.on("llm_output", async (event, ctx) => {
|
|
12894
|
-
|
|
13027
|
+
const usage = event.usage ?? {};
|
|
13028
|
+
const contextTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
13029
|
+
const model = event.model ?? resolveModel(ctx.agentId ?? "main");
|
|
13030
|
+
const contextLimit = getModelContextLimit(model);
|
|
13031
|
+
diag("HOOK_llm_output", {
|
|
13032
|
+
ctx: dumpCtx(ctx),
|
|
13033
|
+
model,
|
|
13034
|
+
tokensIn: usage.input,
|
|
13035
|
+
tokensOut: usage.output,
|
|
13036
|
+
cacheRead: usage.cacheRead,
|
|
13037
|
+
cacheWrite: usage.cacheWrite,
|
|
13038
|
+
contextTokens,
|
|
13039
|
+
contextLimit
|
|
13040
|
+
});
|
|
12895
13041
|
const agentId = ctx.agentId ?? "main";
|
|
12896
13042
|
const agentName = resolveAgentName(agentId);
|
|
12897
13043
|
try {
|
|
12898
|
-
const usage = event.usage ?? {};
|
|
12899
|
-
const ext = event;
|
|
12900
13044
|
const sessionKey = ctx.sessionKey;
|
|
12901
13045
|
tracker.updateFromLlmOutput(agentName, sessionKey, {
|
|
12902
|
-
model
|
|
13046
|
+
model,
|
|
12903
13047
|
tokensIn: usage.input ?? 0,
|
|
12904
13048
|
tokensOut: usage.output ?? 0,
|
|
12905
|
-
contextTokens
|
|
12906
|
-
contextLimit
|
|
13049
|
+
contextTokens,
|
|
13050
|
+
contextLimit
|
|
12907
13051
|
});
|
|
12908
13052
|
if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
|
|
12909
13053
|
tracker.addSession(agentName, sessionKey);
|
|
@@ -12933,10 +13077,9 @@ function registerHooks(api, cfg) {
|
|
|
12933
13077
|
const agentId = ctx.agentId ?? "main";
|
|
12934
13078
|
const agentName = resolveAgentName(agentId);
|
|
12935
13079
|
try {
|
|
12936
|
-
const ext = event;
|
|
12937
13080
|
tracker.updateFromCompaction(agentName, {
|
|
12938
|
-
contextTokens:
|
|
12939
|
-
contextLimit:
|
|
13081
|
+
contextTokens: event.tokenCount ?? 0,
|
|
13082
|
+
contextLimit: getModelContextLimit(resolveModel(agentId))
|
|
12940
13083
|
});
|
|
12941
13084
|
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12942
13085
|
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
@@ -12947,7 +13090,7 @@ function registerHooks(api, cfg) {
|
|
|
12947
13090
|
}
|
|
12948
13091
|
const entry = buildActivityEntry(agentName, "after_compaction", {
|
|
12949
13092
|
messageCount: event.messageCount,
|
|
12950
|
-
compactedCount:
|
|
13093
|
+
compactedCount: event.compactedCount,
|
|
12951
13094
|
sessionKey: ctx.sessionKey
|
|
12952
13095
|
});
|
|
12953
13096
|
if (entry) addActivityToHot(entry);
|
package/dist/package.json
CHANGED