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