@cfio/cohort-sync 0.10.3 → 0.11.1
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 +485 -469
- package/dist/openclaw.plugin.json +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13271,53 +13271,6 @@ var AgentStateTracker = class {
|
|
|
13271
13271
|
}
|
|
13272
13272
|
};
|
|
13273
13273
|
|
|
13274
|
-
// src/pocket-guide.ts
|
|
13275
|
-
var POCKET_GUIDE = `# Cohort Agent Guide (Pocket Version)
|
|
13276
|
-
|
|
13277
|
-
> These are the essential rules. For the full guide, see https://docs.cohort.bot/integration/guide
|
|
13278
|
-
|
|
13279
|
-
## Getting Started
|
|
13280
|
-
- Call the cohort_context tool at the start of every work session for your personalized briefing.
|
|
13281
|
-
- If cohort_context is unavailable, call GET /api/v1/context directly.
|
|
13282
|
-
- Do not skip the context call \u2014 it contains workspace-specific overrides that supersede these rules.
|
|
13283
|
-
|
|
13284
|
-
## Task Lifecycle
|
|
13285
|
-
- Create tasks for trackable work items. Always set priority (default p2) and effort when estimable.
|
|
13286
|
-
- Use POST /tasks/:id/transition for status changes. Never PATCH status directly \u2014 the server rejects it.
|
|
13287
|
-
- If a transition is rejected, read the error response for valid transitions from the current status.
|
|
13288
|
-
- You cannot transition tasks to "done" unless your workspace admin has enabled it. Only humans close tasks by default.
|
|
13289
|
-
- When moving to "in_progress", you are claiming ownership \u2014 only claim tasks you can actively work on.
|
|
13290
|
-
- When moving to "waiting", comment what you are blocked on and who/what can unblock you.
|
|
13291
|
-
- When work is complete, comment with evidence: PR link, test output, or summary of changes.
|
|
13292
|
-
|
|
13293
|
-
## Comments
|
|
13294
|
-
- Comment before every status transition explaining what happened.
|
|
13295
|
-
- Post progress updates every 15-30 minutes on long-running work.
|
|
13296
|
-
- Keep comments factual \u2014 what you did, what you found, what is next.
|
|
13297
|
-
- No conversational filler. Every comment must contain information.
|
|
13298
|
-
- Reference specific files, line numbers, error messages, or URLs when relevant.
|
|
13299
|
-
|
|
13300
|
-
## Projects & Initiatives
|
|
13301
|
-
- Don't create projects or initiatives without explicit instruction.
|
|
13302
|
-
- Assign tasks to existing projects when one fits. Don't create a project for a single task.
|
|
13303
|
-
- If no project fits, leave the task unassigned to a project.
|
|
13304
|
-
|
|
13305
|
-
## Error Recovery
|
|
13306
|
-
- If auth fails (401), stop immediately. Do not retry. Report the failure.
|
|
13307
|
-
- If rate limited (429), wait per Retry-After header.
|
|
13308
|
-
- If 404, verify you are using the correct task number or ID (both accepted).
|
|
13309
|
-
- If 500, retry once after a brief pause. If it fails again, stop and report.
|
|
13310
|
-
|
|
13311
|
-
## What Not To Do
|
|
13312
|
-
- Don't poll /tasks in a loop \u2014 check assignments, then do your work.
|
|
13313
|
-
- Don't create duplicate tasks \u2014 search first.
|
|
13314
|
-
- Don't delete tasks unless explicitly told to.
|
|
13315
|
-
- Don't bulk-create tasks speculatively \u2014 create them as work becomes concrete.
|
|
13316
|
-
- Don't modify tasks assigned to other agents unless coordinating through comments.
|
|
13317
|
-
- Don't set "done" on tasks you didn't work on.
|
|
13318
|
-
- Don't ignore workspace-specific overrides from your context response.
|
|
13319
|
-
`;
|
|
13320
|
-
|
|
13321
13274
|
// src/types.ts
|
|
13322
13275
|
var DEFAULT_API_URL = "https://api.cohort.bot";
|
|
13323
13276
|
|
|
@@ -13381,13 +13334,7 @@ function dumpCtx(ctx) {
|
|
|
13381
13334
|
function dumpEvent(event) {
|
|
13382
13335
|
return dumpCtx(event);
|
|
13383
13336
|
}
|
|
13384
|
-
var PLUGIN_VERSION = true ? "0.
|
|
13385
|
-
var _gatewayStartHandler = null;
|
|
13386
|
-
async function handleGatewayStart(event) {
|
|
13387
|
-
if (_gatewayStartHandler) {
|
|
13388
|
-
await _gatewayStartHandler(event);
|
|
13389
|
-
}
|
|
13390
|
-
}
|
|
13337
|
+
var PLUGIN_VERSION = true ? "0.11.1" : "unknown";
|
|
13391
13338
|
function resolveGatewayToken(api) {
|
|
13392
13339
|
const token = api.config?.gateway?.auth?.token;
|
|
13393
13340
|
return typeof token === "string" ? token : null;
|
|
@@ -13443,21 +13390,9 @@ function resolveIdentity(configIdentity, workspaceDir) {
|
|
|
13443
13390
|
avatar: configIdentity?.avatar ?? fileIdentity?.avatar
|
|
13444
13391
|
};
|
|
13445
13392
|
}
|
|
13446
|
-
|
|
13447
|
-
function getOrCreateTracker() {
|
|
13448
|
-
if (!tracker) tracker = new AgentStateTracker();
|
|
13449
|
-
return tracker;
|
|
13450
|
-
}
|
|
13451
|
-
var gatewayPort = null;
|
|
13452
|
-
var gatewayToken = null;
|
|
13453
|
-
var persistentGwClient = null;
|
|
13454
|
-
var gwClientInitialized = false;
|
|
13455
|
-
var keepaliveInterval = null;
|
|
13456
|
-
var commandUnsubscriber2 = null;
|
|
13457
|
-
var STATE_FILE_PATH = "";
|
|
13458
|
-
function saveSessionsToDisk(tracker2) {
|
|
13393
|
+
function saveSessionsToDisk(tracker, stateFilePath) {
|
|
13459
13394
|
try {
|
|
13460
|
-
const state =
|
|
13395
|
+
const state = tracker.exportState();
|
|
13461
13396
|
const data = {
|
|
13462
13397
|
sessions: [],
|
|
13463
13398
|
sessionKeyToAgent: Object.fromEntries(state.sessionKeyToAgent),
|
|
@@ -13469,27 +13404,27 @@ function saveSessionsToDisk(tracker2) {
|
|
|
13469
13404
|
data.sessions.push({ agentName: name, key });
|
|
13470
13405
|
}
|
|
13471
13406
|
}
|
|
13472
|
-
fs2.writeFileSync(
|
|
13407
|
+
fs2.writeFileSync(stateFilePath, JSON.stringify(data), { mode: 384 });
|
|
13473
13408
|
} catch {
|
|
13474
13409
|
}
|
|
13475
13410
|
}
|
|
13476
|
-
function loadSessionsFromDisk(
|
|
13411
|
+
function loadSessionsFromDisk(tracker, stateFilePath, logger) {
|
|
13477
13412
|
try {
|
|
13478
|
-
if (!fs2.existsSync(
|
|
13479
|
-
const data = JSON.parse(fs2["read"+"FileSync"](
|
|
13413
|
+
if (!fs2.existsSync(stateFilePath)) return;
|
|
13414
|
+
const data = JSON.parse(fs2["read"+"FileSync"](stateFilePath, "utf8"));
|
|
13480
13415
|
if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
|
|
13481
13416
|
logger.info("cohort-sync: disk session state too old (>24h), skipping");
|
|
13482
13417
|
return;
|
|
13483
13418
|
}
|
|
13484
13419
|
let count = 0;
|
|
13485
13420
|
for (const { agentName, key } of data.sessions) {
|
|
13486
|
-
if (!
|
|
13487
|
-
|
|
13421
|
+
if (!tracker.hasSession(agentName, key)) {
|
|
13422
|
+
tracker.addSession(agentName, key);
|
|
13488
13423
|
count++;
|
|
13489
13424
|
}
|
|
13490
13425
|
}
|
|
13491
13426
|
for (const [key, agent] of Object.entries(data.sessionKeyToAgent)) {
|
|
13492
|
-
|
|
13427
|
+
tracker.setSessionAgent(key, agent);
|
|
13493
13428
|
}
|
|
13494
13429
|
for (const [channelId, agent] of Object.entries(data.channelAgents ?? {})) {
|
|
13495
13430
|
setChannelAgent(channelId, agent);
|
|
@@ -13502,8 +13437,6 @@ function loadSessionsFromDisk(tracker2, logger) {
|
|
|
13502
13437
|
}
|
|
13503
13438
|
function initGatewayClient(port, token, cfg, resolveAgentName, logger) {
|
|
13504
13439
|
const client2 = new GatewayClient(port, token, logger, PLUGIN_VERSION);
|
|
13505
|
-
persistentGwClient = client2;
|
|
13506
|
-
gwClientInitialized = true;
|
|
13507
13440
|
const onConnected = async () => {
|
|
13508
13441
|
logger.debug(`cohort-sync: gateway client connected (methods=${client2.availableMethods.size}, events=${client2.availableEvents.size})`);
|
|
13509
13442
|
logger.info(`cohort-sync: gateway client connected (methods=${client2.availableMethods.size}, events=${client2.availableEvents.size})`);
|
|
@@ -13528,75 +13461,109 @@ function initGatewayClient(port, token, cfg, resolveAgentName, logger) {
|
|
|
13528
13461
|
logger.debug("cohort-sync: initial connect deferred", { error: String(err) });
|
|
13529
13462
|
logger.warn(`cohort-sync: GW connect will retry: ${String(err)}`);
|
|
13530
13463
|
});
|
|
13464
|
+
return client2;
|
|
13531
13465
|
}
|
|
13532
|
-
function
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
const tracker2 = getOrCreateTracker();
|
|
13537
|
-
const convexUrl = cfg.convexUrl ?? deriveConvexUrl(cfg.apiUrl);
|
|
13538
|
-
createClient(convexUrl);
|
|
13539
|
-
setLogger(logger);
|
|
13540
|
-
gatewayPort = api.config?.gateway?.port ?? null;
|
|
13541
|
-
gatewayToken = resolveGatewayToken(api);
|
|
13542
|
-
if (gatewayPort && gatewayToken) {
|
|
13543
|
-
initGatewayClient(gatewayPort, gatewayToken, cfg, resolveAgentName, logger);
|
|
13466
|
+
async function handleGatewayStart(event, state) {
|
|
13467
|
+
if (!state) {
|
|
13468
|
+
console.log("[cohort-sync] handleGatewayStart: state is null, skipping");
|
|
13469
|
+
return;
|
|
13544
13470
|
}
|
|
13545
|
-
const
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
}
|
|
13471
|
+
const { cfg, tracker, logger, config, api } = state;
|
|
13472
|
+
logger.info("cohort-sync: handleGatewayStart ENTERED", { port: event.port, hasConfig: !!config, hasCfg: !!cfg });
|
|
13473
|
+
try {
|
|
13474
|
+
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
13475
|
+
});
|
|
13476
|
+
} catch {
|
|
13550
13477
|
}
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13478
|
+
try {
|
|
13479
|
+
const agentList = (config?.agents?.list ?? []).map((a) => ({
|
|
13480
|
+
id: a.id,
|
|
13481
|
+
model: state.resolveModel(a.id),
|
|
13482
|
+
identity: resolveIdentity(a.identity, a.workspace)
|
|
13483
|
+
}));
|
|
13484
|
+
await fullSync(state.resolveAgentName("main"), state.resolveModel("main"), cfg, logger, agentList);
|
|
13485
|
+
} catch (err) {
|
|
13486
|
+
logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
|
|
13487
|
+
}
|
|
13488
|
+
for (const a of config?.agents?.list ?? []) {
|
|
13489
|
+
const agentName = state.resolveAgentName(a.id);
|
|
13490
|
+
const mp = a.messageProvider;
|
|
13491
|
+
logger.debug("cohort-sync: gateway start: seed bridge", { agentId: a.id, agentName, messageProvider: mp, accountId: a.accountId });
|
|
13492
|
+
if (mp && typeof mp === "string") {
|
|
13493
|
+
setChannelAgent(mp, agentName);
|
|
13494
|
+
}
|
|
13495
|
+
}
|
|
13496
|
+
logger.debug("cohort-sync: gateway start: bridge after seed", { bridge: Object.fromEntries(getChannelAgentBridge()) });
|
|
13497
|
+
state.gatewayPort = event.port;
|
|
13498
|
+
const token = resolveGatewayToken(api);
|
|
13499
|
+
if (token) {
|
|
13500
|
+
state.gatewayToken = token;
|
|
13501
|
+
logger.debug("cohort-sync: gateway client connecting", { port: event.port, hasToken: true });
|
|
13502
|
+
try {
|
|
13503
|
+
const client2 = initGatewayClient(event.port, token, cfg, state.resolveAgentName, logger);
|
|
13504
|
+
state.persistentGwClient = client2;
|
|
13505
|
+
state.gwClientInitialized = true;
|
|
13506
|
+
if (state.commandUnsubscriber) {
|
|
13507
|
+
state.commandUnsubscriber();
|
|
13508
|
+
state.commandUnsubscriber = null;
|
|
13509
|
+
}
|
|
13510
|
+
const unsub = startCommandSubscription(cfg, logger, state.resolveAgentName, state.persistentGwClient);
|
|
13511
|
+
state.commandUnsubscriber = unsub;
|
|
13512
|
+
} catch (err) {
|
|
13513
|
+
logger.debug("cohort-sync: gateway client connect failed", { error: String(err) });
|
|
13514
|
+
logger.error(`cohort-sync: gateway client connect failed: ${String(err)}`);
|
|
13515
|
+
}
|
|
13516
|
+
} else {
|
|
13517
|
+
logger.debug("cohort-sync: no gateway token");
|
|
13518
|
+
logger.warn("cohort-sync: no gateway auth token \u2014 cron operations disabled");
|
|
13519
|
+
}
|
|
13520
|
+
await startNotificationSubscription(
|
|
13521
|
+
event.port,
|
|
13522
|
+
cfg,
|
|
13523
|
+
api.config.hooks?.token,
|
|
13524
|
+
logger,
|
|
13525
|
+
state.persistentGwClient
|
|
13526
|
+
).catch((err) => {
|
|
13527
|
+
logger.error(`cohort-sync: subscription init failed: ${String(err)}`);
|
|
13560
13528
|
});
|
|
13561
|
-
const
|
|
13562
|
-
const
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13529
|
+
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
13530
|
+
for (const agentId of allAgentIds) {
|
|
13531
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13532
|
+
try {
|
|
13533
|
+
tracker.setModel(agentName, state.resolveModel(agentId));
|
|
13534
|
+
tracker.updateStatus(agentName, "idle");
|
|
13535
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
13536
|
+
if (snapshot) {
|
|
13537
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
13538
|
+
tracker.markTelemetryPushed(agentName);
|
|
13539
|
+
}
|
|
13540
|
+
} catch (err) {
|
|
13541
|
+
logger.warn(`cohort-sync: initial telemetry seed failed for ${agentName}: ${String(err)}`);
|
|
13570
13542
|
}
|
|
13571
13543
|
}
|
|
13572
|
-
logger.
|
|
13573
|
-
if (
|
|
13574
|
-
|
|
13575
|
-
const
|
|
13576
|
-
|
|
13577
|
-
const
|
|
13578
|
-
if (
|
|
13579
|
-
|
|
13580
|
-
tracker2.markSessionsPushed(agentName);
|
|
13581
|
-
logger.info(`cohort-sync: pushed ${sessSnapshot.length} restored sessions for ${agentName}`);
|
|
13582
|
-
}).catch((err) => {
|
|
13583
|
-
logger.warn(`cohort-sync: failed to push restored sessions for ${agentName}: ${String(err)}`);
|
|
13544
|
+
logger.info(`cohort-sync: seeded telemetry for ${allAgentIds.length} agents`);
|
|
13545
|
+
if (state.keepaliveInterval) clearInterval(state.keepaliveInterval);
|
|
13546
|
+
state.keepaliveInterval = setInterval(async () => {
|
|
13547
|
+
for (const agentId of allAgentIds) {
|
|
13548
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13549
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
13550
|
+
if (snapshot) {
|
|
13551
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION }).catch(() => {
|
|
13584
13552
|
});
|
|
13585
13553
|
}
|
|
13586
13554
|
}
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
function resolveAgentFromContext(ctx) {
|
|
13555
|
+
for (const agentId of allAgentIds) {
|
|
13556
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13557
|
+
tracker.pruneStaleSessions(agentName, 864e5);
|
|
13558
|
+
}
|
|
13559
|
+
saveSessionsToDisk(tracker, state.stateFilePath);
|
|
13560
|
+
}, 15e4);
|
|
13561
|
+
logger.info("cohort-sync: keepalive interval started (150s)");
|
|
13562
|
+
}
|
|
13563
|
+
function registerHookHandlers(api, logger, getState) {
|
|
13564
|
+
function resolveAgentFromContext(state, ctx) {
|
|
13598
13565
|
const allCtxKeys = Object.keys(ctx);
|
|
13599
|
-
logger.debug("cohort-sync: resolve agent: start", {
|
|
13566
|
+
state.logger.debug("cohort-sync: resolve agent: start", {
|
|
13600
13567
|
ctxKeys: allCtxKeys,
|
|
13601
13568
|
agentId: ctx.agentId,
|
|
13602
13569
|
sessionKey: ctx.sessionKey,
|
|
@@ -13608,230 +13575,96 @@ function registerHooks(api, cfg) {
|
|
|
13608
13575
|
workspaceDir: ctx.workspaceDir
|
|
13609
13576
|
});
|
|
13610
13577
|
if (ctx.agentId && typeof ctx.agentId === "string") {
|
|
13611
|
-
const resolved2 = resolveAgentName(ctx.agentId);
|
|
13612
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "agentId", raw: ctx.agentId, resolved: resolved2 });
|
|
13578
|
+
const resolved2 = state.resolveAgentName(ctx.agentId);
|
|
13579
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "agentId", raw: ctx.agentId, resolved: resolved2 });
|
|
13613
13580
|
return resolved2;
|
|
13614
13581
|
}
|
|
13615
13582
|
const sessionKey = ctx.sessionKey ?? ctx.sessionId;
|
|
13616
13583
|
if (sessionKey && typeof sessionKey === "string") {
|
|
13617
|
-
const mapped =
|
|
13584
|
+
const mapped = state.tracker.getSessionAgent(sessionKey);
|
|
13618
13585
|
if (mapped) {
|
|
13619
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "sessionKey_mapped", sessionKey, mapped });
|
|
13586
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "sessionKey_mapped", sessionKey, mapped });
|
|
13620
13587
|
return mapped;
|
|
13621
13588
|
}
|
|
13622
13589
|
const parts = sessionKey.split(":");
|
|
13623
13590
|
if (parts[0] === "agent" && parts.length >= 2) {
|
|
13624
|
-
const resolved2 = resolveAgentName(parts[1]);
|
|
13625
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "sessionKey_parsed", sessionKey, agentPart: parts[1], resolved: resolved2 });
|
|
13591
|
+
const resolved2 = state.resolveAgentName(parts[1]);
|
|
13592
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "sessionKey_parsed", sessionKey, agentPart: parts[1], resolved: resolved2 });
|
|
13626
13593
|
return resolved2;
|
|
13627
13594
|
}
|
|
13628
13595
|
}
|
|
13629
13596
|
const accountId = ctx.accountId;
|
|
13630
13597
|
if (accountId && typeof accountId === "string") {
|
|
13631
|
-
const resolved2 = resolveAgentName(accountId);
|
|
13632
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "accountId", accountId, resolved: resolved2 });
|
|
13598
|
+
const resolved2 = state.resolveAgentName(accountId);
|
|
13599
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "accountId", accountId, resolved: resolved2 });
|
|
13633
13600
|
return resolved2;
|
|
13634
13601
|
}
|
|
13635
13602
|
const channelId = ctx.channelId;
|
|
13636
13603
|
if (channelId && typeof channelId === "string") {
|
|
13637
13604
|
const channelAgent = getChannelAgent(channelId);
|
|
13638
13605
|
if (channelAgent) {
|
|
13639
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "channelId_bridge", channelId, channelAgent, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
|
|
13606
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "channelId_bridge", channelId, channelAgent, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
|
|
13640
13607
|
return channelAgent;
|
|
13641
13608
|
}
|
|
13642
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "channelId_raw", channelId, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
|
|
13609
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "channelId_raw", channelId, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
|
|
13643
13610
|
return String(channelId);
|
|
13644
13611
|
}
|
|
13645
|
-
const resolved = resolveAgentName("main");
|
|
13646
|
-
logger.debug("cohort-sync: resolve agent: result", { method: "fallback_main", resolved });
|
|
13612
|
+
const resolved = state.resolveAgentName("main");
|
|
13613
|
+
state.logger.debug("cohort-sync: resolve agent: result", { method: "fallback_main", resolved });
|
|
13647
13614
|
return resolved;
|
|
13648
13615
|
}
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
}
|
|
13653
|
-
|
|
13654
|
-
return ctx.channelId;
|
|
13655
|
-
}
|
|
13656
|
-
const sessionKey = ctx.sessionKey ?? ctx.sessionId;
|
|
13657
|
-
if (sessionKey && typeof sessionKey === "string") {
|
|
13658
|
-
const parsed = parseSessionKey(sessionKey);
|
|
13659
|
-
return parsed.channel;
|
|
13660
|
-
}
|
|
13661
|
-
return "unknown";
|
|
13662
|
-
}
|
|
13663
|
-
const activityBatch = new MicroBatch({
|
|
13664
|
-
maxSize: 10,
|
|
13665
|
-
maxDelayMs: 1e3,
|
|
13666
|
-
flush: (entries) => pushActivity(cfg.apiKey, entries),
|
|
13667
|
-
onError: (err) => logger.warn(`cohort-sync: activity batch flush failed: ${String(err)}`)
|
|
13668
|
-
});
|
|
13669
|
-
const KEEPALIVE_INTERVAL_MS = 15e4;
|
|
13670
|
-
{
|
|
13671
|
-
if (commandUnsubscriber2) {
|
|
13672
|
-
commandUnsubscriber2();
|
|
13673
|
-
commandUnsubscriber2 = null;
|
|
13674
|
-
}
|
|
13675
|
-
const unsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient);
|
|
13676
|
-
commandUnsubscriber2 = unsub;
|
|
13677
|
-
}
|
|
13678
|
-
function resolveModel(agentId) {
|
|
13679
|
-
const agent = config?.agents?.list?.find((a) => a.id === agentId);
|
|
13680
|
-
const m = agent?.model;
|
|
13681
|
-
if (typeof m === "string") return m;
|
|
13682
|
-
if (m && typeof m === "object" && "primary" in m) return String(m.primary);
|
|
13683
|
-
return "unknown";
|
|
13684
|
-
}
|
|
13685
|
-
function getModelContextLimit(model) {
|
|
13686
|
-
const m = model.toLowerCase();
|
|
13687
|
-
if (m.includes("opus") || m.includes("sonnet") || m.includes("haiku")) return 2e5;
|
|
13688
|
-
if (m.includes("gpt-4o")) return 128e3;
|
|
13689
|
-
if (m.includes("gpt-4-turbo") || m.includes("gpt-4-1")) return 128e3;
|
|
13690
|
-
if (m.includes("gpt-4")) return 8192;
|
|
13691
|
-
if (m.includes("o3") || m.includes("o4-mini")) return 2e5;
|
|
13692
|
-
if (m.includes("gemini-2")) return 1e6;
|
|
13693
|
-
if (m.includes("gemini")) return 128e3;
|
|
13694
|
-
if (m.includes("deepseek")) return 128e3;
|
|
13695
|
-
return 2e5;
|
|
13696
|
-
}
|
|
13697
|
-
_gatewayStartHandler = async (event) => {
|
|
13698
|
-
logger.debug("cohort-sync: hook: gateway_start", { port: event.port, eventKeys: Object.keys(event) });
|
|
13699
|
-
try {
|
|
13700
|
-
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
13701
|
-
});
|
|
13702
|
-
} catch {
|
|
13703
|
-
}
|
|
13704
|
-
try {
|
|
13705
|
-
const agentList = (config?.agents?.list ?? []).map((a) => ({
|
|
13706
|
-
id: a.id,
|
|
13707
|
-
model: resolveModel(a.id),
|
|
13708
|
-
identity: resolveIdentity(a.identity, a.workspace)
|
|
13709
|
-
}));
|
|
13710
|
-
await fullSync(resolveAgentName("main"), resolveModel("main"), cfg, logger, agentList);
|
|
13711
|
-
} catch (err) {
|
|
13712
|
-
logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
|
|
13713
|
-
}
|
|
13714
|
-
for (const a of config?.agents?.list ?? []) {
|
|
13715
|
-
const agentName = resolveAgentName(a.id);
|
|
13716
|
-
const mp = a.messageProvider;
|
|
13717
|
-
logger.debug("cohort-sync: gateway start: seed bridge", { agentId: a.id, agentName, messageProvider: mp, accountId: a.accountId });
|
|
13718
|
-
if (mp && typeof mp === "string") {
|
|
13719
|
-
setChannelAgent(mp, agentName);
|
|
13720
|
-
}
|
|
13721
|
-
}
|
|
13722
|
-
logger.debug("cohort-sync: gateway start: bridge after seed", { bridge: Object.fromEntries(getChannelAgentBridge()) });
|
|
13723
|
-
gatewayPort = event.port;
|
|
13724
|
-
const token = resolveGatewayToken(api);
|
|
13725
|
-
if (token) {
|
|
13726
|
-
gatewayToken = token;
|
|
13727
|
-
logger.debug("cohort-sync: gateway client connecting", { port: event.port, hasToken: true });
|
|
13728
|
-
try {
|
|
13729
|
-
await initGatewayClient(event.port, token, cfg, resolveAgentName, logger);
|
|
13730
|
-
if (commandUnsubscriber2) {
|
|
13731
|
-
commandUnsubscriber2();
|
|
13732
|
-
commandUnsubscriber2 = null;
|
|
13733
|
-
}
|
|
13734
|
-
const unsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient);
|
|
13735
|
-
commandUnsubscriber2 = unsub;
|
|
13736
|
-
} catch (err) {
|
|
13737
|
-
logger.debug("cohort-sync: gateway client connect failed", { error: String(err) });
|
|
13738
|
-
logger.error(`cohort-sync: gateway client connect failed: ${String(err)}`);
|
|
13739
|
-
}
|
|
13740
|
-
} else {
|
|
13741
|
-
logger.debug("cohort-sync: no gateway token");
|
|
13742
|
-
logger.warn("cohort-sync: no gateway auth token \u2014 cron operations disabled");
|
|
13743
|
-
}
|
|
13744
|
-
await startNotificationSubscription(
|
|
13745
|
-
event.port,
|
|
13746
|
-
cfg,
|
|
13747
|
-
api.config.hooks?.token,
|
|
13748
|
-
logger,
|
|
13749
|
-
persistentGwClient
|
|
13750
|
-
).catch((err) => {
|
|
13751
|
-
logger.error(`cohort-sync: subscription init failed: ${String(err)}`);
|
|
13752
|
-
});
|
|
13753
|
-
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
13754
|
-
for (const agentId of allAgentIds) {
|
|
13755
|
-
const agentName = resolveAgentName(agentId);
|
|
13756
|
-
try {
|
|
13757
|
-
tracker2.setModel(agentName, resolveModel(agentId));
|
|
13758
|
-
tracker2.updateStatus(agentName, "idle");
|
|
13759
|
-
const snapshot = tracker2.getTelemetrySnapshot(agentName);
|
|
13760
|
-
if (snapshot) {
|
|
13761
|
-
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
13762
|
-
tracker2.markTelemetryPushed(agentName);
|
|
13763
|
-
}
|
|
13764
|
-
} catch (err) {
|
|
13765
|
-
logger.warn(`cohort-sync: initial telemetry seed failed for ${agentName}: ${String(err)}`);
|
|
13766
|
-
}
|
|
13767
|
-
}
|
|
13768
|
-
logger.info(`cohort-sync: seeded telemetry for ${allAgentIds.length} agents`);
|
|
13769
|
-
if (keepaliveInterval) clearInterval(keepaliveInterval);
|
|
13770
|
-
keepaliveInterval = setInterval(async () => {
|
|
13771
|
-
for (const agentId of allAgentIds) {
|
|
13772
|
-
const agentName = resolveAgentName(agentId);
|
|
13773
|
-
const snapshot = tracker2.getTelemetrySnapshot(agentName);
|
|
13774
|
-
if (snapshot) {
|
|
13775
|
-
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION }).catch(() => {
|
|
13776
|
-
});
|
|
13777
|
-
}
|
|
13778
|
-
}
|
|
13779
|
-
for (const agentId of allAgentIds) {
|
|
13780
|
-
const agentName = resolveAgentName(agentId);
|
|
13781
|
-
tracker2.pruneStaleSessions(agentName, 864e5);
|
|
13782
|
-
}
|
|
13783
|
-
saveSessionsToDisk(tracker2);
|
|
13784
|
-
}, KEEPALIVE_INTERVAL_MS);
|
|
13785
|
-
logger.info(`cohort-sync: keepalive interval started (${KEEPALIVE_INTERVAL_MS / 1e3}s)`);
|
|
13786
|
-
};
|
|
13787
|
-
api.registerHook("agent_end", async (event) => {
|
|
13788
|
-
const ctx = event.context ?? {};
|
|
13789
|
-
logger.debug("cohort-sync: hook: agent_end", { ctx: dumpCtx(ctx), success: ctx.success, error: ctx.error, durationMs: ctx.durationMs });
|
|
13616
|
+
api.on("agent_end", async (event, ctx) => {
|
|
13617
|
+
const state = getState();
|
|
13618
|
+
if (!state) return;
|
|
13619
|
+
const { cfg, tracker, logger: log } = state;
|
|
13620
|
+
log.debug("cohort-sync: hook: agent_end", { ctx: dumpCtx(ctx), success: event.success, error: event.error, durationMs: event.durationMs });
|
|
13790
13621
|
const agentId = ctx.agentId ?? "main";
|
|
13791
|
-
const agentName = resolveAgentName(agentId);
|
|
13622
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13792
13623
|
try {
|
|
13793
|
-
|
|
13794
|
-
await syncAgentStatus(agentName, "idle", resolveModel(agentId), cfg,
|
|
13795
|
-
if (
|
|
13796
|
-
const snapshot =
|
|
13624
|
+
tracker.updateStatus(agentName, "idle");
|
|
13625
|
+
await syncAgentStatus(agentName, "idle", state.resolveModel(agentId), cfg, log);
|
|
13626
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
13627
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
13797
13628
|
if (snapshot) {
|
|
13798
13629
|
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
13799
|
-
|
|
13630
|
+
tracker.markTelemetryPushed(agentName);
|
|
13800
13631
|
}
|
|
13801
13632
|
}
|
|
13802
13633
|
const sessionKey = ctx.sessionKey;
|
|
13803
13634
|
if (sessionKey && sessionKey.includes(":cron:")) {
|
|
13804
13635
|
try {
|
|
13805
|
-
const raw = fs2["read"+"FileSync"](cronStorePath, "utf8");
|
|
13636
|
+
const raw = fs2["read"+"FileSync"](state.cronStorePath, "utf8");
|
|
13806
13637
|
const store = JSON.parse(raw);
|
|
13807
13638
|
const jobs = store.jobs ?? [];
|
|
13808
|
-
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13639
|
+
const mapped = jobs.map((j) => mapCronJob(j, state.resolveAgentName));
|
|
13809
13640
|
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13810
|
-
|
|
13641
|
+
log.debug("cohort-sync: cron agent end push", { count: mapped.length, sessionKey });
|
|
13811
13642
|
} catch (err) {
|
|
13812
|
-
|
|
13643
|
+
log.debug("cohort-sync: cron agent end push failed", { error: String(err) });
|
|
13813
13644
|
}
|
|
13814
13645
|
}
|
|
13815
|
-
if (
|
|
13646
|
+
if (event.success === false) {
|
|
13816
13647
|
const entry = buildActivityEntry(agentName, "agent_end", {
|
|
13817
13648
|
success: false,
|
|
13818
|
-
error:
|
|
13819
|
-
durationMs:
|
|
13649
|
+
error: event.error,
|
|
13650
|
+
durationMs: event.durationMs,
|
|
13820
13651
|
sessionKey: ctx.sessionKey
|
|
13821
13652
|
});
|
|
13822
|
-
if (entry) activityBatch.add(entry);
|
|
13653
|
+
if (entry) state.activityBatch.add(entry);
|
|
13823
13654
|
}
|
|
13824
13655
|
} catch (err) {
|
|
13825
|
-
|
|
13656
|
+
log.warn(`cohort-sync: agent_end sync failed: ${String(err)}`);
|
|
13826
13657
|
}
|
|
13827
|
-
}
|
|
13828
|
-
api.
|
|
13829
|
-
const
|
|
13830
|
-
|
|
13658
|
+
});
|
|
13659
|
+
api.on("llm_output", async (event, ctx) => {
|
|
13660
|
+
const state = getState();
|
|
13661
|
+
if (!state) return;
|
|
13662
|
+
const { cfg, tracker, logger: log } = state;
|
|
13663
|
+
const usage = event.usage ?? {};
|
|
13831
13664
|
const contextTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
13832
|
-
const model =
|
|
13833
|
-
const contextLimit = getModelContextLimit(model);
|
|
13834
|
-
|
|
13665
|
+
const model = event.model ?? state.resolveModel(ctx.agentId ?? "main");
|
|
13666
|
+
const contextLimit = state.getModelContextLimit(model);
|
|
13667
|
+
log.debug("cohort-sync: hook: llm_output", {
|
|
13835
13668
|
ctx: dumpCtx(ctx),
|
|
13836
13669
|
model,
|
|
13837
13670
|
tokensIn: usage.input,
|
|
@@ -13842,106 +13675,112 @@ function registerHooks(api, cfg) {
|
|
|
13842
13675
|
contextLimit
|
|
13843
13676
|
});
|
|
13844
13677
|
const agentId = ctx.agentId ?? "main";
|
|
13845
|
-
const agentName = resolveAgentName(agentId);
|
|
13678
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13846
13679
|
try {
|
|
13847
13680
|
const sessionKey = ctx.sessionKey;
|
|
13848
|
-
|
|
13681
|
+
tracker.updateFromLlmOutput(agentName, sessionKey, {
|
|
13849
13682
|
model,
|
|
13850
13683
|
tokensIn: usage.input ?? 0,
|
|
13851
13684
|
tokensOut: usage.output ?? 0,
|
|
13852
13685
|
contextTokens,
|
|
13853
13686
|
contextLimit
|
|
13854
13687
|
});
|
|
13855
|
-
if (sessionKey && !
|
|
13856
|
-
|
|
13857
|
-
|
|
13688
|
+
if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
|
|
13689
|
+
tracker.addSession(agentName, sessionKey);
|
|
13690
|
+
log.info(`cohort-sync: inferred session for ${agentName} from llm_output (${sessionKey})`);
|
|
13858
13691
|
}
|
|
13859
13692
|
if (sessionKey) {
|
|
13860
|
-
|
|
13693
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
13861
13694
|
}
|
|
13862
|
-
if (
|
|
13863
|
-
const snapshot =
|
|
13695
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
13696
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
13864
13697
|
if (snapshot) {
|
|
13865
13698
|
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
13866
|
-
|
|
13699
|
+
tracker.markTelemetryPushed(agentName);
|
|
13867
13700
|
}
|
|
13868
13701
|
}
|
|
13869
|
-
if (
|
|
13870
|
-
const sessionsSnapshot =
|
|
13702
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
13703
|
+
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
13871
13704
|
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
13872
|
-
|
|
13705
|
+
tracker.markSessionsPushed(agentName);
|
|
13873
13706
|
}
|
|
13874
13707
|
} catch (err) {
|
|
13875
|
-
|
|
13708
|
+
log.warn(`cohort-sync: llm_output telemetry failed: ${String(err)}`);
|
|
13876
13709
|
}
|
|
13877
|
-
}
|
|
13878
|
-
api.
|
|
13879
|
-
const
|
|
13880
|
-
|
|
13710
|
+
});
|
|
13711
|
+
api.on("after_compaction", async (event, ctx) => {
|
|
13712
|
+
const state = getState();
|
|
13713
|
+
if (!state) return;
|
|
13714
|
+
const { cfg, tracker, logger: log } = state;
|
|
13715
|
+
log.debug("cohort-sync: hook: after_compaction", { ctx: dumpCtx(ctx), messageCount: event.messageCount, tokenCount: event.tokenCount });
|
|
13881
13716
|
const agentId = ctx.agentId ?? "main";
|
|
13882
|
-
const agentName = resolveAgentName(agentId);
|
|
13717
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13883
13718
|
try {
|
|
13884
|
-
|
|
13885
|
-
contextTokens:
|
|
13886
|
-
contextLimit: getModelContextLimit(resolveModel(agentId))
|
|
13719
|
+
tracker.updateFromCompaction(agentName, {
|
|
13720
|
+
contextTokens: event.tokenCount ?? 0,
|
|
13721
|
+
contextLimit: state.getModelContextLimit(state.resolveModel(agentId))
|
|
13887
13722
|
});
|
|
13888
|
-
if (
|
|
13889
|
-
const snapshot =
|
|
13723
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
13724
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
13890
13725
|
if (snapshot) {
|
|
13891
13726
|
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
13892
|
-
|
|
13727
|
+
tracker.markTelemetryPushed(agentName);
|
|
13893
13728
|
}
|
|
13894
13729
|
}
|
|
13895
13730
|
const entry = buildActivityEntry(agentName, "after_compaction", {
|
|
13896
|
-
messageCount:
|
|
13897
|
-
compactedCount:
|
|
13731
|
+
messageCount: event.messageCount,
|
|
13732
|
+
compactedCount: event.compactedCount,
|
|
13898
13733
|
sessionKey: ctx.sessionKey
|
|
13899
13734
|
});
|
|
13900
|
-
if (entry) activityBatch.add(entry);
|
|
13735
|
+
if (entry) state.activityBatch.add(entry);
|
|
13901
13736
|
} catch (err) {
|
|
13902
|
-
|
|
13737
|
+
log.warn(`cohort-sync: after_compaction telemetry failed: ${String(err)}`);
|
|
13903
13738
|
}
|
|
13904
|
-
}
|
|
13905
|
-
api.
|
|
13906
|
-
const
|
|
13907
|
-
|
|
13739
|
+
});
|
|
13740
|
+
api.on("before_agent_start", async (event, ctx) => {
|
|
13741
|
+
const state = getState();
|
|
13742
|
+
if (!state) return;
|
|
13743
|
+
const { cfg, tracker, logger: log } = state;
|
|
13744
|
+
log.debug("cohort-sync: hook: before_agent_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
13908
13745
|
const agentId = ctx.agentId ?? "main";
|
|
13909
|
-
const agentName = resolveAgentName(agentId);
|
|
13910
|
-
|
|
13746
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13747
|
+
log.debug("cohort-sync: hook: before_agent_start resolved", { agentId, agentName, ctxChannelId: ctx.channelId, ctxMessageProvider: ctx.messageProvider, ctxSessionKey: ctx.sessionKey, ctxAccountId: ctx.accountId });
|
|
13911
13748
|
try {
|
|
13912
|
-
if (!gwClientInitialized && gatewayPort && gatewayToken) {
|
|
13749
|
+
if (!state.gwClientInitialized && state.gatewayPort && state.gatewayToken) {
|
|
13913
13750
|
try {
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13751
|
+
const client2 = initGatewayClient(state.gatewayPort, state.gatewayToken, cfg, state.resolveAgentName, log);
|
|
13752
|
+
state.persistentGwClient = client2;
|
|
13753
|
+
state.gwClientInitialized = true;
|
|
13754
|
+
if (state.commandUnsubscriber) {
|
|
13755
|
+
state.commandUnsubscriber();
|
|
13756
|
+
state.commandUnsubscriber = null;
|
|
13918
13757
|
}
|
|
13919
|
-
const unsub = startCommandSubscription(cfg,
|
|
13920
|
-
|
|
13758
|
+
const unsub = startCommandSubscription(cfg, log, state.resolveAgentName, state.persistentGwClient);
|
|
13759
|
+
state.commandUnsubscriber = unsub;
|
|
13921
13760
|
} catch (err) {
|
|
13922
|
-
|
|
13761
|
+
log.debug("cohort-sync: gateway client lazy init failed", { error: String(err) });
|
|
13923
13762
|
}
|
|
13924
13763
|
}
|
|
13925
|
-
|
|
13926
|
-
if (
|
|
13927
|
-
const snapshot =
|
|
13764
|
+
tracker.updateStatus(agentName, "working");
|
|
13765
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
13766
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
13928
13767
|
if (snapshot) {
|
|
13929
13768
|
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
13930
|
-
|
|
13769
|
+
tracker.markTelemetryPushed(agentName);
|
|
13931
13770
|
}
|
|
13932
13771
|
}
|
|
13933
13772
|
const sessionKey = ctx.sessionKey;
|
|
13934
|
-
if (sessionKey && !
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
if (
|
|
13939
|
-
const sessSnapshot =
|
|
13773
|
+
if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
|
|
13774
|
+
tracker.addSession(agentName, sessionKey);
|
|
13775
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
13776
|
+
log.info(`cohort-sync: inferred session for ${agentName} (${sessionKey})`);
|
|
13777
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
13778
|
+
const sessSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
13940
13779
|
await pushSessions(cfg.apiKey, agentName, sessSnapshot);
|
|
13941
|
-
|
|
13780
|
+
tracker.markSessionsPushed(agentName);
|
|
13942
13781
|
}
|
|
13943
13782
|
} else if (sessionKey) {
|
|
13944
|
-
|
|
13783
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
13945
13784
|
}
|
|
13946
13785
|
const ctxChannelId = ctx.channelId;
|
|
13947
13786
|
if (ctxChannelId) {
|
|
@@ -13952,193 +13791,362 @@ function registerHooks(api, cfg) {
|
|
|
13952
13791
|
setChannelAgent(mp, agentName);
|
|
13953
13792
|
}
|
|
13954
13793
|
} catch (err) {
|
|
13955
|
-
|
|
13794
|
+
log.warn(`cohort-sync: before_agent_start telemetry failed: ${String(err)}`);
|
|
13956
13795
|
}
|
|
13957
|
-
}
|
|
13958
|
-
api.
|
|
13959
|
-
const
|
|
13960
|
-
|
|
13796
|
+
});
|
|
13797
|
+
api.on("session_start", async (event, ctx) => {
|
|
13798
|
+
const state = getState();
|
|
13799
|
+
if (!state) return;
|
|
13800
|
+
const { cfg, tracker, logger: log } = state;
|
|
13801
|
+
log.debug("cohort-sync: hook: session_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
13961
13802
|
const agentId = ctx.agentId ?? "main";
|
|
13962
|
-
const agentName = resolveAgentName(agentId);
|
|
13803
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13963
13804
|
try {
|
|
13964
|
-
const sessionKey = ctx.sessionId ?? String(Date.now());
|
|
13965
|
-
|
|
13966
|
-
if (
|
|
13967
|
-
const sessionsSnapshot =
|
|
13805
|
+
const sessionKey = event.sessionId ?? ctx.sessionId ?? String(Date.now());
|
|
13806
|
+
tracker.addSession(agentName, sessionKey);
|
|
13807
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
13808
|
+
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
13968
13809
|
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
13969
|
-
|
|
13810
|
+
tracker.markSessionsPushed(agentName);
|
|
13970
13811
|
}
|
|
13971
13812
|
const parsed = parseSessionKey(sessionKey);
|
|
13972
13813
|
const entry = buildActivityEntry(agentName, "session_start", {
|
|
13973
13814
|
channel: parsed.channel,
|
|
13974
13815
|
sessionKey,
|
|
13975
|
-
resumedFrom:
|
|
13816
|
+
resumedFrom: event.resumedFrom
|
|
13976
13817
|
});
|
|
13977
|
-
if (entry) activityBatch.add(entry);
|
|
13818
|
+
if (entry) state.activityBatch.add(entry);
|
|
13978
13819
|
} catch (err) {
|
|
13979
|
-
|
|
13820
|
+
log.warn(`cohort-sync: session_start tracking failed: ${String(err)}`);
|
|
13980
13821
|
}
|
|
13981
|
-
}
|
|
13982
|
-
api.
|
|
13983
|
-
const
|
|
13984
|
-
|
|
13822
|
+
});
|
|
13823
|
+
api.on("session_end", async (event, ctx) => {
|
|
13824
|
+
const state = getState();
|
|
13825
|
+
if (!state) return;
|
|
13826
|
+
const { cfg, tracker, logger: log } = state;
|
|
13827
|
+
log.debug("cohort-sync: hook: session_end", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
13985
13828
|
const agentId = ctx.agentId ?? "main";
|
|
13986
|
-
const agentName = resolveAgentName(agentId);
|
|
13829
|
+
const agentName = state.resolveAgentName(agentId);
|
|
13987
13830
|
try {
|
|
13988
|
-
const sessionKey = ctx.sessionId ?? "";
|
|
13989
|
-
|
|
13990
|
-
if (
|
|
13991
|
-
const sessionsSnapshot =
|
|
13831
|
+
const sessionKey = event.sessionId ?? ctx.sessionId ?? "";
|
|
13832
|
+
tracker.removeSession(agentName, sessionKey);
|
|
13833
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
13834
|
+
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
13992
13835
|
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
13993
|
-
|
|
13836
|
+
tracker.markSessionsPushed(agentName);
|
|
13994
13837
|
}
|
|
13995
13838
|
const entry = buildActivityEntry(agentName, "session_end", {
|
|
13996
13839
|
sessionKey,
|
|
13997
|
-
messageCount:
|
|
13998
|
-
durationMs:
|
|
13840
|
+
messageCount: event.messageCount,
|
|
13841
|
+
durationMs: event.durationMs
|
|
13999
13842
|
});
|
|
14000
|
-
if (entry) activityBatch.add(entry);
|
|
13843
|
+
if (entry) state.activityBatch.add(entry);
|
|
14001
13844
|
} catch (err) {
|
|
14002
|
-
|
|
13845
|
+
log.warn(`cohort-sync: session_end tracking failed: ${String(err)}`);
|
|
14003
13846
|
}
|
|
14004
|
-
}
|
|
14005
|
-
api.
|
|
14006
|
-
const
|
|
14007
|
-
|
|
14008
|
-
|
|
13847
|
+
});
|
|
13848
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
13849
|
+
const state = getState();
|
|
13850
|
+
if (!state) return;
|
|
13851
|
+
state.logger.debug("cohort-sync: hook: after_tool_call", { ctx: dumpCtx(ctx), toolName: event.toolName, error: event.error });
|
|
13852
|
+
const agentName = resolveAgentFromContext(state, ctx);
|
|
14009
13853
|
try {
|
|
14010
13854
|
const entry = buildActivityEntry(agentName, "after_tool_call", {
|
|
14011
|
-
toolName:
|
|
14012
|
-
params:
|
|
14013
|
-
error:
|
|
14014
|
-
durationMs:
|
|
13855
|
+
toolName: event.toolName,
|
|
13856
|
+
params: event.params,
|
|
13857
|
+
error: event.error,
|
|
13858
|
+
durationMs: event.durationMs,
|
|
14015
13859
|
sessionKey: ctx.sessionKey,
|
|
14016
|
-
model: resolveModel(ctx.agentId ?? "main")
|
|
13860
|
+
model: state.resolveModel(ctx.agentId ?? "main")
|
|
14017
13861
|
});
|
|
14018
|
-
if (entry) activityBatch.add(entry);
|
|
13862
|
+
if (entry) state.activityBatch.add(entry);
|
|
14019
13863
|
} catch (err) {
|
|
14020
|
-
logger.warn(`cohort-sync: after_tool_call activity failed: ${String(err)}`);
|
|
13864
|
+
state.logger.warn(`cohort-sync: after_tool_call activity failed: ${String(err)}`);
|
|
14021
13865
|
}
|
|
14022
|
-
}
|
|
14023
|
-
api.
|
|
14024
|
-
const
|
|
14025
|
-
|
|
13866
|
+
});
|
|
13867
|
+
api.on("message_received", async (event, ctx) => {
|
|
13868
|
+
const state = getState();
|
|
13869
|
+
if (!state) return;
|
|
13870
|
+
state.logger.debug("cohort-sync: hook: message_received raw", {
|
|
14026
13871
|
ctx: dumpCtx(ctx),
|
|
14027
13872
|
event: dumpEvent(event),
|
|
14028
13873
|
bridgeStateBefore: Object.fromEntries(getChannelAgentBridge())
|
|
14029
13874
|
});
|
|
14030
|
-
const agentName = resolveAgentFromContext(ctx);
|
|
13875
|
+
const agentName = resolveAgentFromContext(state, ctx);
|
|
14031
13876
|
const channel = ctx.channelId;
|
|
14032
|
-
logger.debug("cohort-sync: hook: message_received resolved", {
|
|
13877
|
+
state.logger.debug("cohort-sync: hook: message_received resolved", {
|
|
14033
13878
|
agentName,
|
|
14034
13879
|
channel,
|
|
14035
13880
|
accountId: ctx.accountId,
|
|
14036
13881
|
conversationId: ctx.conversationId,
|
|
14037
|
-
from:
|
|
13882
|
+
from: event.from
|
|
14038
13883
|
});
|
|
14039
13884
|
try {
|
|
14040
13885
|
const entry = buildActivityEntry(agentName, "message_received", {
|
|
14041
13886
|
channel: channel ?? "unknown"
|
|
14042
13887
|
});
|
|
14043
|
-
if (entry) activityBatch.add(entry);
|
|
13888
|
+
if (entry) state.activityBatch.add(entry);
|
|
14044
13889
|
} catch (err) {
|
|
14045
|
-
logger.warn(`cohort-sync: message_received activity failed: ${String(err)}`);
|
|
13890
|
+
state.logger.warn(`cohort-sync: message_received activity failed: ${String(err)}`);
|
|
14046
13891
|
}
|
|
14047
|
-
}
|
|
14048
|
-
api.
|
|
14049
|
-
const
|
|
14050
|
-
|
|
13892
|
+
});
|
|
13893
|
+
api.on("message_sent", async (event, ctx) => {
|
|
13894
|
+
const state = getState();
|
|
13895
|
+
if (!state) return;
|
|
13896
|
+
state.logger.debug("cohort-sync: hook: message_sent raw", {
|
|
14051
13897
|
ctx: dumpCtx(ctx),
|
|
14052
13898
|
event: dumpEvent(event),
|
|
14053
13899
|
bridgeStateBefore: Object.fromEntries(getChannelAgentBridge())
|
|
14054
13900
|
});
|
|
14055
|
-
const agentName = resolveAgentFromContext(ctx);
|
|
13901
|
+
const agentName = resolveAgentFromContext(state, ctx);
|
|
14056
13902
|
const channel = ctx.channelId;
|
|
14057
|
-
logger.debug("cohort-sync: hook: message_sent resolved", {
|
|
13903
|
+
state.logger.debug("cohort-sync: hook: message_sent resolved", {
|
|
14058
13904
|
agentName,
|
|
14059
13905
|
channel,
|
|
14060
13906
|
accountId: ctx.accountId,
|
|
14061
13907
|
conversationId: ctx.conversationId,
|
|
14062
|
-
to:
|
|
14063
|
-
success:
|
|
14064
|
-
error:
|
|
13908
|
+
to: event.to,
|
|
13909
|
+
success: event.success,
|
|
13910
|
+
error: event.error
|
|
14065
13911
|
});
|
|
14066
13912
|
try {
|
|
14067
13913
|
const entry = buildActivityEntry(agentName, "message_sent", {
|
|
14068
13914
|
channel: channel ?? "unknown",
|
|
14069
|
-
success:
|
|
14070
|
-
error:
|
|
13915
|
+
success: event.success,
|
|
13916
|
+
error: event.error
|
|
14071
13917
|
});
|
|
14072
|
-
if (entry) activityBatch.add(entry);
|
|
13918
|
+
if (entry) state.activityBatch.add(entry);
|
|
14073
13919
|
} catch (err) {
|
|
14074
|
-
logger.warn(`cohort-sync: message_sent activity failed: ${String(err)}`);
|
|
13920
|
+
state.logger.warn(`cohort-sync: message_sent activity failed: ${String(err)}`);
|
|
14075
13921
|
}
|
|
14076
|
-
}
|
|
14077
|
-
api.
|
|
14078
|
-
const
|
|
14079
|
-
|
|
13922
|
+
});
|
|
13923
|
+
api.on("before_compaction", async (event, ctx) => {
|
|
13924
|
+
const state = getState();
|
|
13925
|
+
if (!state) return;
|
|
13926
|
+
state.logger.debug("cohort-sync: hook: before_compaction", { ctx: dumpCtx(ctx) });
|
|
14080
13927
|
const agentId = ctx.agentId ?? "main";
|
|
14081
|
-
const agentName = resolveAgentName(agentId);
|
|
13928
|
+
const agentName = state.resolveAgentName(agentId);
|
|
14082
13929
|
try {
|
|
14083
13930
|
const entry = buildActivityEntry(agentName, "before_compaction", {
|
|
14084
13931
|
sessionKey: ctx.sessionKey
|
|
14085
13932
|
});
|
|
14086
|
-
if (entry) activityBatch.add(entry);
|
|
13933
|
+
if (entry) state.activityBatch.add(entry);
|
|
14087
13934
|
} catch (err) {
|
|
14088
|
-
logger.warn(`cohort-sync: before_compaction activity failed: ${String(err)}`);
|
|
13935
|
+
state.logger.warn(`cohort-sync: before_compaction activity failed: ${String(err)}`);
|
|
14089
13936
|
}
|
|
14090
|
-
}
|
|
14091
|
-
api.
|
|
14092
|
-
const
|
|
14093
|
-
|
|
13937
|
+
});
|
|
13938
|
+
api.on("before_reset", async (event, ctx) => {
|
|
13939
|
+
const state = getState();
|
|
13940
|
+
if (!state) return;
|
|
13941
|
+
state.logger.debug("cohort-sync: hook: before_reset", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
14094
13942
|
const agentId = ctx.agentId ?? "main";
|
|
14095
|
-
const agentName = resolveAgentName(agentId);
|
|
13943
|
+
const agentName = state.resolveAgentName(agentId);
|
|
14096
13944
|
try {
|
|
14097
13945
|
const entry = buildActivityEntry(agentName, "before_reset", {
|
|
14098
|
-
reason:
|
|
13946
|
+
reason: event.reason,
|
|
14099
13947
|
sessionKey: ctx.sessionKey
|
|
14100
13948
|
});
|
|
14101
|
-
if (entry) activityBatch.add(entry);
|
|
13949
|
+
if (entry) state.activityBatch.add(entry);
|
|
14102
13950
|
} catch (err) {
|
|
14103
|
-
logger.warn(`cohort-sync: before_reset activity failed: ${String(err)}`);
|
|
13951
|
+
state.logger.warn(`cohort-sync: before_reset activity failed: ${String(err)}`);
|
|
14104
13952
|
}
|
|
14105
|
-
}
|
|
14106
|
-
api.
|
|
14107
|
-
|
|
14108
|
-
if (
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
13953
|
+
});
|
|
13954
|
+
api.on("gateway_stop", async (_event, _ctx) => {
|
|
13955
|
+
const state = getState();
|
|
13956
|
+
if (!state) return;
|
|
13957
|
+
const { cfg, tracker, logger: log, config } = state;
|
|
13958
|
+
log.debug("cohort-sync: hook: gateway_stop", { bridgeState: Object.fromEntries(getChannelAgentBridge()) });
|
|
13959
|
+
if (state.keepaliveInterval) {
|
|
13960
|
+
clearInterval(state.keepaliveInterval);
|
|
13961
|
+
state.keepaliveInterval = null;
|
|
13962
|
+
}
|
|
13963
|
+
state.activityBatch.drain();
|
|
14113
13964
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
14114
13965
|
for (const agentId of allAgentIds) {
|
|
14115
|
-
const agentName = resolveAgentName(agentId);
|
|
13966
|
+
const agentName = state.resolveAgentName(agentId);
|
|
14116
13967
|
try {
|
|
14117
|
-
|
|
14118
|
-
const snapshot =
|
|
13968
|
+
tracker.updateStatus(agentName, "unreachable");
|
|
13969
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
14119
13970
|
if (snapshot) {
|
|
14120
13971
|
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
14121
13972
|
}
|
|
14122
13973
|
} catch (err) {
|
|
14123
|
-
|
|
13974
|
+
log.warn(`cohort-sync: final unreachable push failed for ${agentName}: ${String(err)}`);
|
|
14124
13975
|
}
|
|
14125
13976
|
}
|
|
14126
|
-
saveSessionsToDisk(
|
|
14127
|
-
if (persistentGwClient) {
|
|
14128
|
-
persistentGwClient.close();
|
|
14129
|
-
persistentGwClient = null;
|
|
14130
|
-
gwClientInitialized = false;
|
|
13977
|
+
saveSessionsToDisk(tracker, state.stateFilePath);
|
|
13978
|
+
if (state.persistentGwClient) {
|
|
13979
|
+
state.persistentGwClient.close();
|
|
13980
|
+
state.persistentGwClient = null;
|
|
13981
|
+
state.gwClientInitialized = false;
|
|
14131
13982
|
}
|
|
14132
13983
|
try {
|
|
14133
|
-
await markAllUnreachable(cfg,
|
|
13984
|
+
await markAllUnreachable(cfg, log);
|
|
14134
13985
|
} catch (err) {
|
|
14135
|
-
|
|
13986
|
+
log.warn(`cohort-sync: markAllUnreachable failed: ${String(err)}`);
|
|
14136
13987
|
}
|
|
14137
|
-
|
|
13988
|
+
tracker.clear();
|
|
14138
13989
|
closeBridge();
|
|
14139
|
-
|
|
14140
|
-
}
|
|
13990
|
+
log.info("cohort-sync: gateway stopped, all resources cleaned up");
|
|
13991
|
+
});
|
|
14141
13992
|
}
|
|
13993
|
+
function initializeHookState(api, cfg) {
|
|
13994
|
+
const { logger, config } = api;
|
|
13995
|
+
const stateFilePath = path2.join(cfg.stateDir, "session-state.json");
|
|
13996
|
+
const nameMap = cfg.agentNameMap;
|
|
13997
|
+
const tracker = new AgentStateTracker();
|
|
13998
|
+
const convexUrl = cfg.convexUrl ?? deriveConvexUrl(cfg.apiUrl);
|
|
13999
|
+
createClient(convexUrl);
|
|
14000
|
+
setLogger(logger);
|
|
14001
|
+
const identityNameMap = {};
|
|
14002
|
+
const mainIdentity = parseIdentityFile(process.cwd());
|
|
14003
|
+
if (mainIdentity?.name) {
|
|
14004
|
+
identityNameMap["main"] = mainIdentity.name.toLowerCase();
|
|
14005
|
+
}
|
|
14006
|
+
for (const agent of config?.agents?.list ?? []) {
|
|
14007
|
+
const identity = resolveIdentity(agent.identity, agent.workspace);
|
|
14008
|
+
if (identity?.name) {
|
|
14009
|
+
identityNameMap[agent.id] = identity.name.toLowerCase();
|
|
14010
|
+
}
|
|
14011
|
+
}
|
|
14012
|
+
logger.debug("cohort-sync: identity name map", { identityNameMap });
|
|
14013
|
+
function resolveAgentName(agentId) {
|
|
14014
|
+
return (nameMap?.[agentId] ?? identityNameMap[agentId] ?? agentId).toLowerCase();
|
|
14015
|
+
}
|
|
14016
|
+
function resolveModel(agentId) {
|
|
14017
|
+
const agent = config?.agents?.list?.find((a) => a.id === agentId);
|
|
14018
|
+
const m = agent?.model;
|
|
14019
|
+
if (typeof m === "string") return m;
|
|
14020
|
+
if (m && typeof m === "object" && "primary" in m) return String(m.primary);
|
|
14021
|
+
return "unknown";
|
|
14022
|
+
}
|
|
14023
|
+
function getModelContextLimit(model) {
|
|
14024
|
+
const m = model.toLowerCase();
|
|
14025
|
+
if (m.includes("opus") || m.includes("sonnet") || m.includes("haiku")) return 2e5;
|
|
14026
|
+
if (m.includes("gpt-4o")) return 128e3;
|
|
14027
|
+
if (m.includes("gpt-4-turbo") || m.includes("gpt-4-1")) return 128e3;
|
|
14028
|
+
if (m.includes("gpt-4")) return 8192;
|
|
14029
|
+
if (m.includes("o3") || m.includes("o4-mini")) return 2e5;
|
|
14030
|
+
if (m.includes("gemini-2")) return 1e6;
|
|
14031
|
+
if (m.includes("gemini")) return 128e3;
|
|
14032
|
+
if (m.includes("deepseek")) return 128e3;
|
|
14033
|
+
return 2e5;
|
|
14034
|
+
}
|
|
14035
|
+
const cronStorePath = api.config?.cron?.store ?? path2.join(os2.homedir(), ".openclaw", "cron", "jobs.json");
|
|
14036
|
+
const activityBatch = new MicroBatch({
|
|
14037
|
+
maxSize: 10,
|
|
14038
|
+
maxDelayMs: 1e3,
|
|
14039
|
+
flush: (entries) => pushActivity(cfg.apiKey, entries),
|
|
14040
|
+
onError: (err) => logger.warn(`cohort-sync: activity batch flush failed: ${String(err)}`)
|
|
14041
|
+
});
|
|
14042
|
+
const gatewayPort = api.config?.gateway?.port ?? null;
|
|
14043
|
+
const gatewayToken = resolveGatewayToken(api);
|
|
14044
|
+
let persistentGwClient = null;
|
|
14045
|
+
let gwClientInitialized = false;
|
|
14046
|
+
if (gatewayPort && gatewayToken) {
|
|
14047
|
+
const client2 = initGatewayClient(gatewayPort, gatewayToken, cfg, resolveAgentName, logger);
|
|
14048
|
+
persistentGwClient = client2;
|
|
14049
|
+
gwClientInitialized = true;
|
|
14050
|
+
}
|
|
14051
|
+
const commandUnsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient);
|
|
14052
|
+
setToolRuntime({
|
|
14053
|
+
apiKey: cfg.apiKey,
|
|
14054
|
+
apiUrl: cfg.apiUrl,
|
|
14055
|
+
resolveAgentName,
|
|
14056
|
+
logger
|
|
14057
|
+
});
|
|
14058
|
+
if (tracker.getAgentNames().length === 0) {
|
|
14059
|
+
loadSessionsFromDisk(tracker, stateFilePath, logger);
|
|
14060
|
+
const restoredAgents = tracker.getAgentNames();
|
|
14061
|
+
for (const agentName of restoredAgents) {
|
|
14062
|
+
const sessSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
14063
|
+
if (sessSnapshot.length > 0) {
|
|
14064
|
+
pushSessions(cfg.apiKey, agentName, sessSnapshot).then(() => {
|
|
14065
|
+
tracker.markSessionsPushed(agentName);
|
|
14066
|
+
logger.info(`cohort-sync: pushed ${sessSnapshot.length} restored sessions for ${agentName}`);
|
|
14067
|
+
}).catch((err) => {
|
|
14068
|
+
logger.warn(`cohort-sync: failed to push restored sessions for ${agentName}: ${String(err)}`);
|
|
14069
|
+
});
|
|
14070
|
+
}
|
|
14071
|
+
}
|
|
14072
|
+
}
|
|
14073
|
+
logger.info(`cohort-sync: initializeHookState v${PLUGIN_VERSION}`);
|
|
14074
|
+
logger.info("cohort-sync: state initialized", {
|
|
14075
|
+
PLUGIN_VERSION,
|
|
14076
|
+
hasNameMap: !!nameMap,
|
|
14077
|
+
nameMapKeys: nameMap ? Object.keys(nameMap) : [],
|
|
14078
|
+
nameMapValues: nameMap ? Object.values(nameMap) : [],
|
|
14079
|
+
agentCount: config?.agents?.list?.length ?? 0,
|
|
14080
|
+
agentIds: (config?.agents?.list ?? []).map((a) => a.id),
|
|
14081
|
+
agentMessageProviders: (config?.agents?.list ?? []).map((a) => ({ id: a.id, mp: a.messageProvider }))
|
|
14082
|
+
});
|
|
14083
|
+
return {
|
|
14084
|
+
cfg,
|
|
14085
|
+
tracker,
|
|
14086
|
+
logger,
|
|
14087
|
+
config,
|
|
14088
|
+
resolveAgentName,
|
|
14089
|
+
resolveModel,
|
|
14090
|
+
getModelContextLimit,
|
|
14091
|
+
activityBatch,
|
|
14092
|
+
cronStorePath,
|
|
14093
|
+
stateFilePath,
|
|
14094
|
+
gatewayPort,
|
|
14095
|
+
gatewayToken,
|
|
14096
|
+
persistentGwClient,
|
|
14097
|
+
gwClientInitialized,
|
|
14098
|
+
keepaliveInterval: null,
|
|
14099
|
+
commandUnsubscriber: commandUnsub,
|
|
14100
|
+
api
|
|
14101
|
+
};
|
|
14102
|
+
}
|
|
14103
|
+
|
|
14104
|
+
// src/pocket-guide.ts
|
|
14105
|
+
var POCKET_GUIDE = `# Cohort Agent Guide (Pocket Version)
|
|
14106
|
+
|
|
14107
|
+
> These are the essential rules. For the full guide, see https://docs.cohort.bot/integration/guide
|
|
14108
|
+
|
|
14109
|
+
## Getting Started
|
|
14110
|
+
- Call the cohort_context tool at the start of every work session for your personalized briefing.
|
|
14111
|
+
- If cohort_context is unavailable, call GET /api/v1/context directly.
|
|
14112
|
+
- Do not skip the context call \u2014 it contains workspace-specific overrides that supersede these rules.
|
|
14113
|
+
|
|
14114
|
+
## Task Lifecycle
|
|
14115
|
+
- Create tasks for trackable work items. Always set priority (default p2) and effort when estimable.
|
|
14116
|
+
- Use POST /tasks/:id/transition for status changes. Never PATCH status directly \u2014 the server rejects it.
|
|
14117
|
+
- If a transition is rejected, read the error response for valid transitions from the current status.
|
|
14118
|
+
- You cannot transition tasks to "done" unless your workspace admin has enabled it. Only humans close tasks by default.
|
|
14119
|
+
- When moving to "in_progress", you are claiming ownership \u2014 only claim tasks you can actively work on.
|
|
14120
|
+
- When moving to "waiting", comment what you are blocked on and who/what can unblock you.
|
|
14121
|
+
- When work is complete, comment with evidence: PR link, test output, or summary of changes.
|
|
14122
|
+
|
|
14123
|
+
## Comments
|
|
14124
|
+
- Comment before every status transition explaining what happened.
|
|
14125
|
+
- Post progress updates every 15-30 minutes on long-running work.
|
|
14126
|
+
- Keep comments factual \u2014 what you did, what you found, what is next.
|
|
14127
|
+
- No conversational filler. Every comment must contain information.
|
|
14128
|
+
- Reference specific files, line numbers, error messages, or URLs when relevant.
|
|
14129
|
+
|
|
14130
|
+
## Projects & Initiatives
|
|
14131
|
+
- Don't create projects or initiatives without explicit instruction.
|
|
14132
|
+
- Assign tasks to existing projects when one fits. Don't create a project for a single task.
|
|
14133
|
+
- If no project fits, leave the task unassigned to a project.
|
|
14134
|
+
|
|
14135
|
+
## Error Recovery
|
|
14136
|
+
- If auth fails (401), stop immediately. Do not retry. Report the failure.
|
|
14137
|
+
- If rate limited (429), wait per Retry-After header.
|
|
14138
|
+
- If 404, verify you are using the correct task number or ID (both accepted).
|
|
14139
|
+
- If 500, retry once after a brief pause. If it fails again, stop and report.
|
|
14140
|
+
|
|
14141
|
+
## What Not To Do
|
|
14142
|
+
- Don't poll /tasks in a loop \u2014 check assignments, then do your work.
|
|
14143
|
+
- Don't create duplicate tasks \u2014 search first.
|
|
14144
|
+
- Don't delete tasks unless explicitly told to.
|
|
14145
|
+
- Don't bulk-create tasks speculatively \u2014 create them as work becomes concrete.
|
|
14146
|
+
- Don't modify tasks assigned to other agents unless coordinating through comments.
|
|
14147
|
+
- Don't set "done" on tasks you didn't work on.
|
|
14148
|
+
- Don't ignore workspace-specific overrides from your context response.
|
|
14149
|
+
`;
|
|
14142
14150
|
|
|
14143
14151
|
// index.ts
|
|
14144
14152
|
function textResult(text, details) {
|
|
@@ -14166,14 +14174,21 @@ var plugin = {
|
|
|
14166
14174
|
);
|
|
14167
14175
|
return;
|
|
14168
14176
|
}
|
|
14169
|
-
|
|
14177
|
+
let hookState = null;
|
|
14178
|
+
registerHookHandlers(api, api.logger, () => hookState);
|
|
14179
|
+
const gatewayPort = api.config?.gateway?.port ?? 18789;
|
|
14170
14180
|
api.registerHook(
|
|
14171
14181
|
"gateway:startup",
|
|
14172
14182
|
async (...args) => {
|
|
14173
14183
|
const event = args[0] ?? {};
|
|
14174
|
-
const port = event?.port ??
|
|
14175
|
-
api.logger.info(`cohort-sync: gateway:startup hook fired (port=${port})`);
|
|
14176
|
-
|
|
14184
|
+
const port = event?.port ?? gatewayPort;
|
|
14185
|
+
api.logger.info(`cohort-sync: gateway:startup hook fired (port=${port}, hookState=${hookState ? "SET" : "NULL"})`);
|
|
14186
|
+
try {
|
|
14187
|
+
await handleGatewayStart({ ...event, port }, hookState);
|
|
14188
|
+
api.logger.info("cohort-sync: handleGatewayStart completed");
|
|
14189
|
+
} catch (err) {
|
|
14190
|
+
api.logger.error(`cohort-sync: handleGatewayStart THREW: ${err instanceof Error ? err.stack : String(err)}`);
|
|
14191
|
+
}
|
|
14177
14192
|
},
|
|
14178
14193
|
{
|
|
14179
14194
|
name: "cohort-sync.gateway-startup",
|
|
@@ -14420,7 +14435,7 @@ Do not attempt more comments until tomorrow.`);
|
|
|
14420
14435
|
return;
|
|
14421
14436
|
}
|
|
14422
14437
|
api.logger.info(`cohort-sync: activated (api: ${apiUrl2})`);
|
|
14423
|
-
|
|
14438
|
+
hookState = initializeHookState(api, {
|
|
14424
14439
|
apiUrl: apiUrl2,
|
|
14425
14440
|
apiKey: apiKey2,
|
|
14426
14441
|
stateDir: svcCtx.stateDir,
|
|
@@ -14428,6 +14443,7 @@ Do not attempt more comments until tomorrow.`);
|
|
|
14428
14443
|
});
|
|
14429
14444
|
},
|
|
14430
14445
|
async stop() {
|
|
14446
|
+
hookState = null;
|
|
14431
14447
|
closeBridge();
|
|
14432
14448
|
api.logger.info("cohort-sync: service stopped");
|
|
14433
14449
|
}
|
package/dist/package.json
CHANGED
package/package.json
CHANGED