@hasna/assistants 1.1.4 → 1.1.6

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.
Files changed (4) hide show
  1. package/dist/cli.js +5360 -2077
  2. package/dist/lib.d.ts +4 -2
  3. package/dist/lib.js +2415 -417
  4. package/package.json +1 -2
package/dist/lib.js CHANGED
@@ -240,7 +240,21 @@ var init_runtime = __esm(async () => {
240
240
  // ../shared/src/utils.ts
241
241
  import { randomUUID } from "crypto";
242
242
  function generateId() {
243
- return randomUUID();
243
+ const base = randomUUID();
244
+ if (base !== lastUuidBase) {
245
+ lastUuidBase = base;
246
+ uuidCounter = 0;
247
+ return base;
248
+ }
249
+ uuidCounter = uuidCounter + 1 & 281474976710655;
250
+ const parts = base.split("-");
251
+ if (parts.length !== 5) {
252
+ return base;
253
+ }
254
+ const baseValue = BigInt(`0x${parts[4]}`);
255
+ const nextValue = baseValue + BigInt(uuidCounter) & 0xffffffffffffn;
256
+ const nextLast = nextValue.toString(16).padStart(12, "0");
257
+ return `${parts[0]}-${parts[1]}-${parts[2]}-${parts[3]}-${nextLast}`;
244
258
  }
245
259
  function now() {
246
260
  return Date.now();
@@ -302,6 +316,7 @@ function substituteVariables(template, args, env = {}) {
302
316
  });
303
317
  return result;
304
318
  }
319
+ var lastUuidBase = "", uuidCounter = 0;
305
320
  var init_utils = () => {};
306
321
 
307
322
  // ../shared/src/models.ts
@@ -6887,6 +6902,22 @@ function mergeConfig(base, override) {
6887
6902
  ...override.messages?.storage || {}
6888
6903
  }
6889
6904
  },
6905
+ webhooks: {
6906
+ ...base.webhooks || {},
6907
+ ...override.webhooks || {},
6908
+ injection: {
6909
+ ...base.webhooks?.injection || {},
6910
+ ...override.webhooks?.injection || {}
6911
+ },
6912
+ storage: {
6913
+ ...base.webhooks?.storage || {},
6914
+ ...override.webhooks?.storage || {}
6915
+ },
6916
+ security: {
6917
+ ...base.webhooks?.security || {},
6918
+ ...override.webhooks?.security || {}
6919
+ }
6920
+ },
6890
6921
  memory: {
6891
6922
  ...base.memory || {},
6892
6923
  ...override.memory || {},
@@ -7027,7 +7058,8 @@ async function ensureConfigDir(sessionId) {
7027
7058
  mkdir2(join(configDir, "state"), { recursive: true }),
7028
7059
  mkdir2(join(configDir, "energy"), { recursive: true }),
7029
7060
  mkdir2(join(configDir, "jobs"), { recursive: true }),
7030
- mkdir2(join(configDir, "inbox"), { recursive: true })
7061
+ mkdir2(join(configDir, "inbox"), { recursive: true }),
7062
+ mkdir2(join(configDir, "webhooks"), { recursive: true })
7031
7063
  ];
7032
7064
  if (sessionId) {
7033
7065
  dirs.push(mkdir2(join(configDir, "temp", sessionId), { recursive: true }));
@@ -7227,6 +7259,21 @@ var init_config = __esm(async () => {
7227
7259
  maxAgeDays: 90
7228
7260
  }
7229
7261
  },
7262
+ webhooks: {
7263
+ enabled: false,
7264
+ injection: {
7265
+ enabled: true,
7266
+ maxPerTurn: 5
7267
+ },
7268
+ storage: {
7269
+ maxEvents: 1000,
7270
+ maxAgeDays: 30
7271
+ },
7272
+ security: {
7273
+ maxTimestampAgeMs: 300000,
7274
+ rateLimitPerMinute: 60
7275
+ }
7276
+ },
7230
7277
  memory: {
7231
7278
  enabled: true,
7232
7279
  injection: {
@@ -13120,8 +13167,8 @@ var init_evaluator = __esm(() => {
13120
13167
  });
13121
13168
 
13122
13169
  // ../core/src/guardrails/store.ts
13123
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
13124
- import { join as join21, dirname as dirname9 } from "path";
13170
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
13171
+ import { join as join22, dirname as dirname10 } from "path";
13125
13172
  import { createHash as createHash3 } from "crypto";
13126
13173
  function generatePolicyId(name, scope) {
13127
13174
  const hash = createHash3("sha256").update(`${name}-${scope}-${Date.now()}`).digest("hex").slice(0, 8);
@@ -13143,16 +13190,16 @@ class GuardrailsStore {
13143
13190
  getFilePath(location) {
13144
13191
  switch (location) {
13145
13192
  case "user":
13146
- return join21(getConfigDir(), "guardrails.json");
13193
+ return join22(getConfigDir(), "guardrails.json");
13147
13194
  case "project":
13148
- return join21(this.cwd, ".assistants", "guardrails.json");
13195
+ return join22(this.cwd, ".assistants", "guardrails.json");
13149
13196
  case "local":
13150
- return join21(this.cwd, ".assistants", "guardrails.local.json");
13197
+ return join22(this.cwd, ".assistants", "guardrails.local.json");
13151
13198
  }
13152
13199
  }
13153
13200
  loadFrom(location) {
13154
13201
  const filePath = this.getFilePath(location);
13155
- if (!existsSync13(filePath)) {
13202
+ if (!existsSync14(filePath)) {
13156
13203
  return null;
13157
13204
  }
13158
13205
  try {
@@ -13167,9 +13214,9 @@ class GuardrailsStore {
13167
13214
  }
13168
13215
  save(location, config) {
13169
13216
  const filePath = this.getFilePath(location);
13170
- const dir = dirname9(filePath);
13171
- if (!existsSync13(dir)) {
13172
- mkdirSync7(dir, { recursive: true });
13217
+ const dir = dirname10(filePath);
13218
+ if (!existsSync14(dir)) {
13219
+ mkdirSync8(dir, { recursive: true });
13173
13220
  }
13174
13221
  ensurePolicyIds(config);
13175
13222
  writeFileSync8(filePath, JSON.stringify({ guardrails: config }, null, 2), "utf-8");
@@ -42572,8 +42619,8 @@ var require_core3 = __commonJS((exports) => {
42572
42619
  function many1(p) {
42573
42620
  return ab(p, many(p), (head, tail) => [head, ...tail]);
42574
42621
  }
42575
- function ab(pa, pb, join22) {
42576
- return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join22(ma.value, vb, data, i, j)));
42622
+ function ab(pa, pb, join23) {
42623
+ return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join23(ma.value, vb, data, i, j)));
42577
42624
  }
42578
42625
  function left(pa, pb) {
42579
42626
  return ab(pa, pb, (va) => va);
@@ -42581,8 +42628,8 @@ var require_core3 = __commonJS((exports) => {
42581
42628
  function right(pa, pb) {
42582
42629
  return ab(pa, pb, (va, vb) => vb);
42583
42630
  }
42584
- function abc(pa, pb, pc, join22) {
42585
- return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join22(ma.value, mb.value, vc, data, i, j))));
42631
+ function abc(pa, pb, pc, join23) {
42632
+ return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join23(ma.value, mb.value, vc, data, i, j))));
42586
42633
  }
42587
42634
  function middle(pa, pb, pc) {
42588
42635
  return abc(pa, pb, pc, (ra, rb) => rb);
@@ -51296,14 +51343,14 @@ var init_email_parser = __esm(() => {
51296
51343
  });
51297
51344
 
51298
51345
  // ../core/src/workspace/shared.ts
51299
- import { join as join22 } from "path";
51346
+ import { join as join23 } from "path";
51300
51347
  import { homedir as homedir11 } from "os";
51301
51348
  import {
51302
- existsSync as existsSync14,
51303
- mkdirSync as mkdirSync8,
51349
+ existsSync as existsSync15,
51350
+ mkdirSync as mkdirSync9,
51304
51351
  writeFileSync as writeFileSync9,
51305
51352
  readFileSync as readFileSync8,
51306
- readdirSync as readdirSync5,
51353
+ readdirSync as readdirSync6,
51307
51354
  rmSync,
51308
51355
  renameSync
51309
51356
  } from "fs";
@@ -51312,33 +51359,33 @@ class SharedWorkspaceManager {
51312
51359
  basePath;
51313
51360
  constructor(basePath) {
51314
51361
  const envHome = process.env.HOME || process.env.USERPROFILE || homedir11();
51315
- this.basePath = basePath || join22(envHome, ".assistants", "workspaces");
51362
+ this.basePath = basePath || join23(envHome, ".assistants", "workspaces");
51316
51363
  this.ensureDir();
51317
51364
  this.migrateAgentsToAssistants();
51318
51365
  }
51319
51366
  ensureDir() {
51320
- if (!existsSync14(this.basePath)) {
51321
- mkdirSync8(this.basePath, { recursive: true });
51367
+ if (!existsSync15(this.basePath)) {
51368
+ mkdirSync9(this.basePath, { recursive: true });
51322
51369
  }
51323
51370
  }
51324
51371
  migrateAgentsToAssistants() {
51325
51372
  try {
51326
- const dirs = readdirSync5(this.basePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
51373
+ const dirs = readdirSync6(this.basePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
51327
51374
  for (const dir of dirs) {
51328
- const wsPath = join22(this.basePath, dir);
51329
- const oldAgentsDir = join22(wsPath, "agents");
51330
- const newAssistantsDir = join22(wsPath, "assistants");
51331
- if (existsSync14(oldAgentsDir) && !existsSync14(newAssistantsDir)) {
51375
+ const wsPath = join23(this.basePath, dir);
51376
+ const oldAgentsDir = join23(wsPath, "agents");
51377
+ const newAssistantsDir = join23(wsPath, "assistants");
51378
+ if (existsSync15(oldAgentsDir) && !existsSync15(newAssistantsDir)) {
51332
51379
  renameSync(oldAgentsDir, newAssistantsDir);
51333
51380
  }
51334
51381
  }
51335
51382
  } catch {}
51336
51383
  }
51337
51384
  getWorkspacePath(id) {
51338
- return join22(this.basePath, id);
51385
+ return join23(this.basePath, id);
51339
51386
  }
51340
51387
  getMetadataPath(id) {
51341
- return join22(this.getWorkspacePath(id), "workspace.json");
51388
+ return join23(this.getWorkspacePath(id), "workspace.json");
51342
51389
  }
51343
51390
  create(name, createdBy, participants, description) {
51344
51391
  const id = `ws_${generateId().slice(0, 8)}`;
@@ -51353,9 +51400,9 @@ class SharedWorkspaceManager {
51353
51400
  status: "active"
51354
51401
  };
51355
51402
  const wsPath = this.getWorkspacePath(id);
51356
- mkdirSync8(join22(wsPath, "shared"), { recursive: true });
51403
+ mkdirSync9(join23(wsPath, "shared"), { recursive: true });
51357
51404
  for (const assistantId of workspace.participants) {
51358
- mkdirSync8(join22(wsPath, "assistants", assistantId), { recursive: true });
51405
+ mkdirSync9(join23(wsPath, "assistants", assistantId), { recursive: true });
51359
51406
  }
51360
51407
  writeFileSync9(this.getMetadataPath(id), JSON.stringify(workspace, null, 2));
51361
51408
  return workspace;
@@ -51370,15 +51417,15 @@ class SharedWorkspaceManager {
51370
51417
  workspace.updatedAt = Date.now();
51371
51418
  writeFileSync9(this.getMetadataPath(workspaceId), JSON.stringify(workspace, null, 2));
51372
51419
  }
51373
- const assistantDir = join22(this.getWorkspacePath(workspaceId), "assistants", assistantId);
51374
- if (!existsSync14(assistantDir)) {
51375
- mkdirSync8(assistantDir, { recursive: true });
51420
+ const assistantDir = join23(this.getWorkspacePath(workspaceId), "assistants", assistantId);
51421
+ if (!existsSync15(assistantDir)) {
51422
+ mkdirSync9(assistantDir, { recursive: true });
51376
51423
  }
51377
51424
  }
51378
51425
  get(workspaceId) {
51379
51426
  try {
51380
51427
  const metadataPath = this.getMetadataPath(workspaceId);
51381
- if (!existsSync14(metadataPath))
51428
+ if (!existsSync15(metadataPath))
51382
51429
  return null;
51383
51430
  return JSON.parse(readFileSync8(metadataPath, "utf-8"));
51384
51431
  } catch {
@@ -51389,15 +51436,15 @@ class SharedWorkspaceManager {
51389
51436
  return this.getWorkspacePath(workspaceId);
51390
51437
  }
51391
51438
  getSharedPath(workspaceId) {
51392
- return join22(this.getWorkspacePath(workspaceId), "shared");
51439
+ return join23(this.getWorkspacePath(workspaceId), "shared");
51393
51440
  }
51394
51441
  getAssistantPath(workspaceId, assistantId) {
51395
- return join22(this.getWorkspacePath(workspaceId), "assistants", assistantId);
51442
+ return join23(this.getWorkspacePath(workspaceId), "assistants", assistantId);
51396
51443
  }
51397
51444
  list(includeArchived = false) {
51398
51445
  try {
51399
51446
  this.ensureDir();
51400
- const dirs = readdirSync5(this.basePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
51447
+ const dirs = readdirSync6(this.basePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
51401
51448
  const workspaces = [];
51402
51449
  for (const dir of dirs) {
51403
51450
  const workspace = this.get(dir);
@@ -51425,7 +51472,7 @@ class SharedWorkspaceManager {
51425
51472
  }
51426
51473
  delete(workspaceId) {
51427
51474
  const wsPath = this.getWorkspacePath(workspaceId);
51428
- if (existsSync14(wsPath)) {
51475
+ if (existsSync15(wsPath)) {
51429
51476
  rmSync(wsPath, { recursive: true, force: true });
51430
51477
  }
51431
51478
  }
@@ -51485,9 +51532,9 @@ var init_defaults2 = __esm(() => {
51485
51532
  });
51486
51533
 
51487
51534
  // ../core/src/budget/tracker.ts
51488
- import { join as join23, dirname as dirname10 } from "path";
51535
+ import { join as join24, dirname as dirname11 } from "path";
51489
51536
  import { homedir as homedir12 } from "os";
51490
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync9 } from "fs";
51537
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10, readFileSync as readFileSync9 } from "fs";
51491
51538
  function createEmptyUsage() {
51492
51539
  const now2 = new Date().toISOString();
51493
51540
  return {
@@ -51521,16 +51568,16 @@ class BudgetTracker {
51521
51568
  }
51522
51569
  getStatePath() {
51523
51570
  const envHome = process.env.HOME || process.env.USERPROFILE || homedir12();
51524
- return join23(envHome, ".assistants", "budget", `${this.sessionId}.json`);
51571
+ return join24(envHome, ".assistants", "budget", `${this.sessionId}.json`);
51525
51572
  }
51526
51573
  getProjectStatePath(projectId) {
51527
51574
  const envHome = process.env.HOME || process.env.USERPROFILE || homedir12();
51528
- return join23(envHome, ".assistants", "budget", `project-${projectId}.json`);
51575
+ return join24(envHome, ".assistants", "budget", `project-${projectId}.json`);
51529
51576
  }
51530
51577
  loadState() {
51531
51578
  try {
51532
51579
  const statePath = this.getStatePath();
51533
- if (!existsSync15(statePath))
51580
+ if (!existsSync16(statePath))
51534
51581
  return;
51535
51582
  const data = JSON.parse(readFileSync9(statePath, "utf-8"));
51536
51583
  if (data.version !== PERSISTENCE_VERSION && data.version !== 1)
@@ -51551,7 +51598,7 @@ class BudgetTracker {
51551
51598
  loadProjectState(projectId) {
51552
51599
  try {
51553
51600
  const statePath = this.getProjectStatePath(projectId);
51554
- if (!existsSync15(statePath))
51601
+ if (!existsSync16(statePath))
51555
51602
  return createEmptyUsage();
51556
51603
  const data = JSON.parse(readFileSync9(statePath, "utf-8"));
51557
51604
  return data;
@@ -51564,9 +51611,9 @@ class BudgetTracker {
51564
51611
  return;
51565
51612
  try {
51566
51613
  const statePath = this.getProjectStatePath(projectId);
51567
- const stateDir = dirname10(statePath);
51568
- if (!existsSync15(stateDir)) {
51569
- mkdirSync9(stateDir, { recursive: true });
51614
+ const stateDir = dirname11(statePath);
51615
+ if (!existsSync16(stateDir)) {
51616
+ mkdirSync10(stateDir, { recursive: true });
51570
51617
  }
51571
51618
  writeFileSync10(statePath, JSON.stringify(usage, null, 2));
51572
51619
  } catch {}
@@ -51576,9 +51623,9 @@ class BudgetTracker {
51576
51623
  return;
51577
51624
  try {
51578
51625
  const statePath = this.getStatePath();
51579
- const stateDir = dirname10(statePath);
51580
- if (!existsSync15(stateDir)) {
51581
- mkdirSync9(stateDir, { recursive: true });
51626
+ const stateDir = dirname11(statePath);
51627
+ if (!existsSync16(stateDir)) {
51628
+ mkdirSync10(stateDir, { recursive: true });
51582
51629
  }
51583
51630
  const state = {
51584
51631
  version: PERSISTENCE_VERSION,
@@ -52151,8 +52198,8 @@ var init_types2 = __esm(() => {
52151
52198
  });
52152
52199
 
52153
52200
  // ../core/src/registry/store.ts
52154
- import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
52155
- import { join as join24, dirname as dirname11 } from "path";
52201
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
52202
+ import { join as join25, dirname as dirname12 } from "path";
52156
52203
  import { homedir as homedir13 } from "os";
52157
52204
  function generateAssistantId() {
52158
52205
  return `assistant_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
@@ -52290,12 +52337,12 @@ class RegistryStore {
52290
52337
  return this.config.storagePath;
52291
52338
  }
52292
52339
  const home = process.env.HOME || process.env.USERPROFILE || homedir13();
52293
- return join24(home, ".assistants", "registry", "assistants.json");
52340
+ return join25(home, ".assistants", "registry", "assistants.json");
52294
52341
  }
52295
52342
  loadFromFile() {
52296
52343
  try {
52297
52344
  const path = this.getStoragePath();
52298
- if (!existsSync16(path))
52345
+ if (!existsSync17(path))
52299
52346
  return;
52300
52347
  const data = JSON.parse(readFileSync10(path, "utf-8"));
52301
52348
  if (Array.isArray(data.assistants)) {
@@ -52310,9 +52357,9 @@ class RegistryStore {
52310
52357
  return;
52311
52358
  try {
52312
52359
  const path = this.getStoragePath();
52313
- const dir = dirname11(path);
52314
- if (!existsSync16(dir)) {
52315
- mkdirSync10(dir, { recursive: true });
52360
+ const dir = dirname12(path);
52361
+ if (!existsSync17(dir)) {
52362
+ mkdirSync11(dir, { recursive: true });
52316
52363
  }
52317
52364
  const data = {
52318
52365
  version: 1,
@@ -61274,14 +61321,14 @@ var exports_anthropic = {};
61274
61321
  __export(exports_anthropic, {
61275
61322
  AnthropicClient: () => AnthropicClient
61276
61323
  });
61277
- import { readFileSync as readFileSync11, existsSync as existsSync18 } from "fs";
61324
+ import { readFileSync as readFileSync11, existsSync as existsSync19 } from "fs";
61278
61325
  import { homedir as homedir15 } from "os";
61279
- import { join as join26 } from "path";
61326
+ import { join as join27 } from "path";
61280
61327
  function loadApiKeyFromSecrets() {
61281
61328
  const envHome = process.env.HOME || process.env.USERPROFILE;
61282
61329
  const homeDir = envHome && envHome.trim().length > 0 ? envHome : homedir15();
61283
- const secretsPath = join26(homeDir, ".secrets");
61284
- if (existsSync18(secretsPath)) {
61330
+ const secretsPath = join27(homeDir, ".secrets");
61331
+ if (existsSync19(secretsPath)) {
61285
61332
  try {
61286
61333
  const content = readFileSync11(secretsPath, "utf-8");
61287
61334
  const match = content.match(/export\s+ANTHROPIC_API_KEY\s*=\s*["']?([^"'\n]+)["']?/);
@@ -67297,14 +67344,14 @@ var exports_openai = {};
67297
67344
  __export(exports_openai, {
67298
67345
  OpenAIClient: () => OpenAIClient
67299
67346
  });
67300
- import { readFileSync as readFileSync12, existsSync as existsSync19 } from "fs";
67347
+ import { readFileSync as readFileSync12, existsSync as existsSync20 } from "fs";
67301
67348
  import { homedir as homedir16 } from "os";
67302
- import { join as join27 } from "path";
67349
+ import { join as join28 } from "path";
67303
67350
  function loadApiKeyFromSecrets2() {
67304
67351
  const envHome = process.env.HOME || process.env.USERPROFILE;
67305
67352
  const homeDir = envHome && envHome.trim().length > 0 ? envHome : homedir16();
67306
- const secretsPath = join27(homeDir, ".secrets");
67307
- if (existsSync19(secretsPath)) {
67353
+ const secretsPath = join28(homeDir, ".secrets");
67354
+ if (existsSync20(secretsPath)) {
67308
67355
  try {
67309
67356
  const content = readFileSync12(secretsPath, "utf-8");
67310
67357
  const match = content.match(/export\s+OPENAI_API_KEY\s*=\s*["']?([^"'\n]+)["']?/);
@@ -80501,7 +80548,7 @@ var require_readFile = __commonJS((exports) => {
80501
80548
  var promises_1 = __require("fs/promises");
80502
80549
  exports.filePromises = {};
80503
80550
  exports.fileIntercept = {};
80504
- var readFile13 = (path2, options) => {
80551
+ var readFile14 = (path2, options) => {
80505
80552
  if (exports.fileIntercept[path2] !== undefined) {
80506
80553
  return exports.fileIntercept[path2];
80507
80554
  }
@@ -80510,7 +80557,7 @@ var require_readFile = __commonJS((exports) => {
80510
80557
  }
80511
80558
  return exports.filePromises[path2];
80512
80559
  };
80513
- exports.readFile = readFile13;
80560
+ exports.readFile = readFile14;
80514
80561
  });
80515
80562
 
80516
80563
  // ../../node_modules/.bun/@smithy+shared-ini-file-loader@4.4.3/node_modules/@smithy/shared-ini-file-loader/dist-cjs/index.js
@@ -80520,7 +80567,7 @@ var require_dist_cjs35 = __commonJS((exports) => {
80520
80567
  var getSSOTokenFromFile = require_getSSOTokenFromFile();
80521
80568
  var path2 = __require("path");
80522
80569
  var types11 = require_dist_cjs();
80523
- var readFile13 = require_readFile();
80570
+ var readFile14 = require_readFile();
80524
80571
  var ENV_PROFILE = "AWS_PROFILE";
80525
80572
  var DEFAULT_PROFILE2 = "default";
80526
80573
  var getProfileName = (init2) => init2.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE2;
@@ -80604,10 +80651,10 @@ var require_dist_cjs35 = __commonJS((exports) => {
80604
80651
  resolvedConfigFilepath = path2.join(homeDir, configFilepath.slice(2));
80605
80652
  }
80606
80653
  const parsedFiles = await Promise.all([
80607
- readFile13.readFile(resolvedConfigFilepath, {
80654
+ readFile14.readFile(resolvedConfigFilepath, {
80608
80655
  ignoreCache: init2.ignoreCache
80609
80656
  }).then(parseIni).then(getConfigData).catch(swallowError$1),
80610
- readFile13.readFile(resolvedFilepath, {
80657
+ readFile14.readFile(resolvedFilepath, {
80611
80658
  ignoreCache: init2.ignoreCache
80612
80659
  }).then(parseIni).catch(swallowError$1)
80613
80660
  ]);
@@ -80618,7 +80665,7 @@ var require_dist_cjs35 = __commonJS((exports) => {
80618
80665
  };
80619
80666
  var getSsoSessionData = (data) => Object.entries(data).filter(([key]) => key.startsWith(types11.IniSectionType.SSO_SESSION + CONFIG_PREFIX_SEPARATOR)).reduce((acc, [key, value]) => ({ ...acc, [key.substring(key.indexOf(CONFIG_PREFIX_SEPARATOR) + 1)]: value }), {});
80620
80667
  var swallowError = () => ({});
80621
- var loadSsoSessionData = async (init2 = {}) => readFile13.readFile(init2.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
80668
+ var loadSsoSessionData = async (init2 = {}) => readFile14.readFile(init2.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
80622
80669
  var mergeConfigFiles = (...files) => {
80623
80670
  const merged = {};
80624
80671
  for (const file of files) {
@@ -80638,10 +80685,10 @@ var require_dist_cjs35 = __commonJS((exports) => {
80638
80685
  };
80639
80686
  var externalDataInterceptor = {
80640
80687
  getFileRecord() {
80641
- return readFile13.fileIntercept;
80688
+ return readFile14.fileIntercept;
80642
80689
  },
80643
80690
  interceptFile(path3, contents) {
80644
- readFile13.fileIntercept[path3] = Promise.resolve(contents);
80691
+ readFile14.fileIntercept[path3] = Promise.resolve(contents);
80645
80692
  },
80646
80693
  getTokenRecord() {
80647
80694
  return getSSOTokenFromFile.tokenIntercept;
@@ -80659,7 +80706,7 @@ var require_dist_cjs35 = __commonJS((exports) => {
80659
80706
  Object.defineProperty(exports, "readFile", {
80660
80707
  enumerable: true,
80661
80708
  get: function() {
80662
- return readFile13.readFile;
80709
+ return readFile14.readFile;
80663
80710
  }
80664
80711
  });
80665
80712
  exports.CONFIG_PREFIX_SEPARATOR = CONFIG_PREFIX_SEPARATOR;
@@ -87996,7 +88043,7 @@ var require_signin = __commonJS((exports) => {
87996
88043
  import { createHash as createHash4, createPrivateKey, createPublicKey, sign } from "crypto";
87997
88044
  import { promises as fs2 } from "fs";
87998
88045
  import { homedir as homedir18 } from "os";
87999
- import { dirname as dirname15, join as join35 } from "path";
88046
+ import { dirname as dirname16, join as join36 } from "path";
88000
88047
  var import_property_provider18, import_protocol_http10, import_shared_ini_file_loader6, LoginCredentialsFetcher;
88001
88048
  var init_LoginCredentialsFetcher = __esm(() => {
88002
88049
  import_property_provider18 = __toESM(require_dist_cjs17(), 1);
@@ -88149,17 +88196,17 @@ var init_LoginCredentialsFetcher = __esm(() => {
88149
88196
  }
88150
88197
  async saveToken(token) {
88151
88198
  const tokenFilePath = this.getTokenFilePath();
88152
- const directory = dirname15(tokenFilePath);
88199
+ const directory = dirname16(tokenFilePath);
88153
88200
  try {
88154
88201
  await fs2.mkdir(directory, { recursive: true });
88155
88202
  } catch (error2) {}
88156
88203
  await fs2.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
88157
88204
  }
88158
88205
  getTokenFilePath() {
88159
- const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join35(homedir18(), ".aws", "login", "cache");
88206
+ const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join36(homedir18(), ".aws", "login", "cache");
88160
88207
  const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
88161
88208
  const loginSessionSha256 = createHash4("sha256").update(loginSessionBytes).digest("hex");
88162
- return join35(directory, `${loginSessionSha256}.json`);
88209
+ return join36(directory, `${loginSessionSha256}.json`);
88163
88210
  }
88164
88211
  derToRawSignature(derSignature) {
88165
88212
  let offset = 2;
@@ -89198,8 +89245,8 @@ function many(p4) {
89198
89245
  function many1(p4) {
89199
89246
  return ab2(p4, many(p4), (head, tail) => [head, ...tail]);
89200
89247
  }
89201
- function ab2(pa, pb, join37) {
89202
- return (data, i4) => mapOuter(pa(data, i4), (ma) => mapInner(pb(data, ma.position), (vb, j4) => join37(ma.value, vb, data, i4, j4)));
89248
+ function ab2(pa, pb, join38) {
89249
+ return (data, i4) => mapOuter(pa(data, i4), (ma) => mapInner(pb(data, ma.position), (vb, j4) => join38(ma.value, vb, data, i4, j4)));
89203
89250
  }
89204
89251
  function left(pa, pb) {
89205
89252
  return ab2(pa, pb, (va) => va);
@@ -89207,8 +89254,8 @@ function left(pa, pb) {
89207
89254
  function right(pa, pb) {
89208
89255
  return ab2(pa, pb, (va, vb) => vb);
89209
89256
  }
89210
- function abc(pa, pb, pc, join37) {
89211
- return (data, i4) => mapOuter(pa(data, i4), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j4) => join37(ma.value, mb.value, vc, data, i4, j4))));
89257
+ function abc(pa, pb, pc, join38) {
89258
+ return (data, i4) => mapOuter(pa(data, i4), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j4) => join38(ma.value, mb.value, vc, data, i4, j4))));
89212
89259
  }
89213
89260
  function middle(pa, pb, pc) {
89214
89261
  return abc(pa, pb, pc, (ra, rb) => rb);
@@ -110706,12 +110753,70 @@ var init_node2 = __esm(() => {
110706
110753
  decoder = new TextDecoder("utf-8");
110707
110754
  });
110708
110755
 
110756
+ // ../core/src/webhooks/crypto.ts
110757
+ var exports_crypto = {};
110758
+ __export(exports_crypto, {
110759
+ verifySignature: () => verifySignature,
110760
+ signPayload: () => signPayload,
110761
+ isTimestampValid: () => isTimestampValid,
110762
+ generateWebhookSecret: () => generateWebhookSecret,
110763
+ generateWebhookId: () => generateWebhookId,
110764
+ generateEventId: () => generateEventId,
110765
+ generateDeliveryId: () => generateDeliveryId
110766
+ });
110767
+ import { createHmac, randomBytes, timingSafeEqual } from "crypto";
110768
+ function generateWebhookSecret() {
110769
+ const bytes = randomBytes(32);
110770
+ return `whsec_${bytes.toString("hex")}`;
110771
+ }
110772
+ function signPayload(payload, secret) {
110773
+ const key = secret.startsWith("whsec_") ? secret.slice(6) : secret;
110774
+ return createHmac("sha256", key).update(payload).digest("hex");
110775
+ }
110776
+ function verifySignature(payload, signature, secret) {
110777
+ try {
110778
+ const expected = signPayload(payload, secret);
110779
+ const sigBuffer = Buffer.from(signature, "hex");
110780
+ const expectedBuffer = Buffer.from(expected, "hex");
110781
+ if (sigBuffer.length !== expectedBuffer.length) {
110782
+ return false;
110783
+ }
110784
+ return timingSafeEqual(sigBuffer, expectedBuffer);
110785
+ } catch {
110786
+ return false;
110787
+ }
110788
+ }
110789
+ function isTimestampValid(timestamp, maxAgeMs = 300000) {
110790
+ try {
110791
+ const eventTime = new Date(timestamp).getTime();
110792
+ if (isNaN(eventTime))
110793
+ return false;
110794
+ const now2 = Date.now();
110795
+ const age = Math.abs(now2 - eventTime);
110796
+ return age <= maxAgeMs;
110797
+ } catch {
110798
+ return false;
110799
+ }
110800
+ }
110801
+ function generateWebhookId() {
110802
+ return `whk_${generateId().slice(0, 12)}`;
110803
+ }
110804
+ function generateEventId() {
110805
+ return `evt_${generateId().slice(0, 12)}`;
110806
+ }
110807
+ function generateDeliveryId() {
110808
+ return `dlv_${generateId().slice(0, 12)}`;
110809
+ }
110810
+ var init_crypto = __esm(() => {
110811
+ init_src2();
110812
+ });
110813
+
110709
110814
  // ../core/src/index.ts
110710
110815
  await init_runtime();
110711
110816
 
110712
110817
  // ../core/src/agent/loop.ts
110713
110818
  init_src2();
110714
- import { join as join43 } from "path";
110819
+ import { join as join46 } from "path";
110715
110820
 
110716
110821
  // ../core/src/agent/context.ts
110717
110822
  init_src2();
@@ -115766,16 +115871,191 @@ function registerEnergyTools(registry, context) {
115766
115871
  }
115767
115872
  }
115768
115873
 
115874
+ // ../core/src/heartbeat/history.ts
115875
+ await init_config();
115876
+ import { dirname as dirname4, join as join6 } from "path";
115877
+ import { mkdirSync as mkdirSync2, existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
115878
+ import { appendFile as appendFile2, readFile as readFile2 } from "fs/promises";
115879
+ function applySessionPlaceholder(path, sessionId) {
115880
+ if (path.includes("{sessionId}")) {
115881
+ return path.replace("{sessionId}", sessionId);
115882
+ }
115883
+ return path;
115884
+ }
115885
+ function resolveHeartbeatPersistPath(sessionId, persistPath) {
115886
+ if (persistPath) {
115887
+ return applySessionPlaceholder(persistPath, sessionId);
115888
+ }
115889
+ return join6(getConfigDir(), "heartbeats", `${sessionId}.json`);
115890
+ }
115891
+ function resolveHeartbeatHistoryPath(sessionId, historyPath) {
115892
+ if (historyPath) {
115893
+ return applySessionPlaceholder(historyPath, sessionId);
115894
+ }
115895
+ return join6(getConfigDir(), "heartbeats", "runs", `${sessionId}.jsonl`);
115896
+ }
115897
+ function listHeartbeatHistorySessions(baseDir) {
115898
+ const dir = baseDir ?? join6(getConfigDir(), "heartbeats", "runs");
115899
+ if (!existsSync4(dir))
115900
+ return [];
115901
+ return readdirSync2(dir).filter((file) => file.endsWith(".jsonl")).map((file) => file.replace(/\.jsonl$/, ""));
115902
+ }
115903
+ async function appendHeartbeatHistory(historyPath, heartbeat) {
115904
+ try {
115905
+ mkdirSync2(dirname4(historyPath), { recursive: true });
115906
+ await appendFile2(historyPath, `${JSON.stringify(heartbeat)}
115907
+ `, "utf-8");
115908
+ } catch {}
115909
+ }
115910
+ async function readHeartbeatHistory(historyPath, options = {}) {
115911
+ try {
115912
+ const content = await readFile2(historyPath, "utf-8");
115913
+ const lines = content.split(`
115914
+ `).filter(Boolean);
115915
+ const parsed = [];
115916
+ for (const line of lines) {
115917
+ try {
115918
+ parsed.push(JSON.parse(line));
115919
+ } catch {}
115920
+ }
115921
+ const order = options.order ?? "desc";
115922
+ const ordered = order === "desc" ? parsed.reverse() : parsed;
115923
+ if (options.limit && options.limit > 0) {
115924
+ return ordered.slice(0, options.limit);
115925
+ }
115926
+ return ordered;
115927
+ } catch {
115928
+ return [];
115929
+ }
115930
+ }
115931
+ async function readLatestHeartbeat(persistPath, historyPath) {
115932
+ if (historyPath) {
115933
+ const history = await readHeartbeatHistory(historyPath, { limit: 1, order: "desc" });
115934
+ if (history.length > 0)
115935
+ return history[0];
115936
+ }
115937
+ try {
115938
+ const content = await readFile2(persistPath, "utf-8");
115939
+ return JSON.parse(content);
115940
+ } catch {
115941
+ return null;
115942
+ }
115943
+ }
115944
+
115945
+ // ../core/src/tools/heartbeat.ts
115946
+ var heartbeatStatusTool = {
115947
+ name: "heartbeat_status",
115948
+ description: "Get heartbeat status, last heartbeat record, and optional recent runs for a session.",
115949
+ parameters: {
115950
+ type: "object",
115951
+ properties: {
115952
+ sessionId: {
115953
+ type: "string",
115954
+ description: "Optional session id (defaults to current session)"
115955
+ },
115956
+ includeRuns: {
115957
+ type: "boolean",
115958
+ description: "Include recent heartbeat runs in the response"
115959
+ },
115960
+ limit: {
115961
+ type: "number",
115962
+ description: "Max number of runs to include (default: 20)"
115963
+ }
115964
+ },
115965
+ required: []
115966
+ }
115967
+ };
115968
+ var heartbeatRunsTool = {
115969
+ name: "heartbeat_runs",
115970
+ description: "List heartbeat runs for a session with optional limits and ordering.",
115971
+ parameters: {
115972
+ type: "object",
115973
+ properties: {
115974
+ sessionId: {
115975
+ type: "string",
115976
+ description: "Optional session id (defaults to current session)"
115977
+ },
115978
+ limit: {
115979
+ type: "number",
115980
+ description: "Max number of runs to return (default: 50)"
115981
+ },
115982
+ order: {
115983
+ type: "string",
115984
+ description: 'Sort order: "asc" or "desc" (default: desc)',
115985
+ enum: ["asc", "desc"]
115986
+ }
115987
+ },
115988
+ required: []
115989
+ }
115990
+ };
115991
+ var heartbeatTools = [heartbeatStatusTool, heartbeatRunsTool];
115992
+ function parseLimit(value, fallback) {
115993
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
115994
+ return Math.floor(value);
115995
+ }
115996
+ return fallback;
115997
+ }
115998
+ function createHeartbeatToolExecutors(context) {
115999
+ return {
116000
+ heartbeat_status: async (input) => {
116001
+ const sessionId = typeof input.sessionId === "string" && input.sessionId.trim().length > 0 ? input.sessionId.trim() : context.sessionId;
116002
+ const config = context.getHeartbeatConfig?.() ?? null;
116003
+ const persistPath = resolveHeartbeatPersistPath(sessionId, config?.persistPath);
116004
+ const historyPath = resolveHeartbeatHistoryPath(sessionId, config?.historyPath);
116005
+ const lastHeartbeat = await readLatestHeartbeat(persistPath, historyPath);
116006
+ const state = context.getHeartbeatState?.() ?? null;
116007
+ const staleThreshold = config?.staleThresholdMs ?? 120000;
116008
+ const lastTimestamp = lastHeartbeat ? new Date(lastHeartbeat.timestamp).getTime() : null;
116009
+ const computedStale = lastTimestamp ? Date.now() - lastTimestamp > staleThreshold : true;
116010
+ const includeRuns = Boolean(input.includeRuns);
116011
+ const limit = parseLimit(input.limit, 20);
116012
+ const runs = includeRuns ? await readHeartbeatHistory(historyPath, { limit, order: "desc" }) : undefined;
116013
+ const enabled = state?.enabled ?? (config ? true : false);
116014
+ return JSON.stringify({
116015
+ success: true,
116016
+ sessionId,
116017
+ enabled,
116018
+ state: state?.state ?? lastHeartbeat?.state ?? null,
116019
+ isStale: state?.isStale ?? computedStale,
116020
+ lastActivity: state?.lastActivity ?? lastHeartbeat?.lastActivity ?? null,
116021
+ uptimeSeconds: state?.uptimeSeconds ?? lastHeartbeat?.stats?.uptimeSeconds ?? null,
116022
+ lastHeartbeat,
116023
+ runs
116024
+ });
116025
+ },
116026
+ heartbeat_runs: async (input) => {
116027
+ const sessionId = typeof input.sessionId === "string" && input.sessionId.trim().length > 0 ? input.sessionId.trim() : context.sessionId;
116028
+ const config = context.getHeartbeatConfig?.() ?? null;
116029
+ const historyPath = resolveHeartbeatHistoryPath(sessionId, config?.historyPath);
116030
+ const order = input.order === "asc" ? "asc" : "desc";
116031
+ const limit = parseLimit(input.limit, 50);
116032
+ const runs = await readHeartbeatHistory(historyPath, { limit, order });
116033
+ return JSON.stringify({
116034
+ success: true,
116035
+ sessionId,
116036
+ count: runs.length,
116037
+ runs
116038
+ });
116039
+ }
116040
+ };
116041
+ }
116042
+ function registerHeartbeatTools(registry, context) {
116043
+ const executors = createHeartbeatToolExecutors(context);
116044
+ for (const tool of heartbeatTools) {
116045
+ registry.register(tool, executors[tool.name]);
116046
+ }
116047
+ }
116048
+
115769
116049
  // ../core/src/tools/context-entries.ts
115770
116050
  init_src2();
115771
116051
 
115772
116052
  // ../core/src/projects/store.ts
115773
116053
  init_src2();
115774
- import { join as join6 } from "path";
115775
- import { mkdir as mkdir4, readdir, readFile as readFile2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
116054
+ import { join as join7 } from "path";
116055
+ import { mkdir as mkdir4, readdir, readFile as readFile3, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
115776
116056
  var SAFE_ID_PATTERN2 = /^[a-zA-Z0-9_-]+$/;
115777
116057
  function projectsDir(cwd) {
115778
- return join6(cwd, ".assistants", "projects");
116058
+ return join7(cwd, ".assistants", "projects");
115779
116059
  }
115780
116060
  function isSafeId2(id) {
115781
116061
  return SAFE_ID_PATTERN2.test(id);
@@ -115783,7 +116063,7 @@ function isSafeId2(id) {
115783
116063
  function projectPath(cwd, id) {
115784
116064
  if (!isSafeId2(id))
115785
116065
  return null;
115786
- return join6(projectsDir(cwd), `${id}.json`);
116066
+ return join7(projectsDir(cwd), `${id}.json`);
115787
116067
  }
115788
116068
  async function ensureProjectsDir(cwd) {
115789
116069
  await mkdir4(projectsDir(cwd), { recursive: true });
@@ -115800,7 +116080,7 @@ async function listProjects(cwd) {
115800
116080
  if (!file.endsWith(".json"))
115801
116081
  continue;
115802
116082
  try {
115803
- const raw = await readFile2(join6(dir, file), "utf-8");
116083
+ const raw = await readFile3(join7(dir, file), "utf-8");
115804
116084
  const parsed = JSON.parse(raw);
115805
116085
  if (parsed?.id && parsed?.name) {
115806
116086
  projects.push(parsed);
@@ -115817,7 +116097,7 @@ async function readProject(cwd, id) {
115817
116097
  const path = projectPath(cwd, id);
115818
116098
  if (!path)
115819
116099
  return null;
115820
- const raw = await readFile2(path, "utf-8");
116100
+ const raw = await readFile3(path, "utf-8");
115821
116101
  const project = JSON.parse(raw);
115822
116102
  if (!project?.id || !project?.name)
115823
116103
  return null;
@@ -115884,9 +116164,9 @@ function hasProjectNameConflict(projects, name) {
115884
116164
  }
115885
116165
 
115886
116166
  // ../core/src/projects/context.ts
115887
- import { readFile as readFile3 } from "fs/promises";
116167
+ import { readFile as readFile4 } from "fs/promises";
115888
116168
  import { homedir as homedir5 } from "os";
115889
- import { resolve as resolve2, join as join7 } from "path";
116169
+ import { resolve as resolve2, join as join8 } from "path";
115890
116170
 
115891
116171
  // ../core/src/validation/paths.ts
115892
116172
  import { resolve, normalize, relative, isAbsolute } from "path";
@@ -115967,7 +116247,7 @@ function normalizeEntryLabel(entry) {
115967
116247
  }
115968
116248
  async function renderFileEntry(entry, options) {
115969
116249
  const rawPath = entry.value.trim();
115970
- const expandedPath = rawPath === "~" ? homedir5() : rawPath.startsWith("~/") ? join7(homedir5(), rawPath.slice(2)) : rawPath;
116250
+ const expandedPath = rawPath === "~" ? homedir5() : rawPath.startsWith("~/") ? join8(homedir5(), rawPath.slice(2)) : rawPath;
115971
116251
  const resolved = resolve2(options.cwd, expandedPath);
115972
116252
  const validation = await validatePath(resolved, { allowedPaths: [options.cwd] });
115973
116253
  if (!validation.valid) {
@@ -115975,7 +116255,7 @@ async function renderFileEntry(entry, options) {
115975
116255
  }
115976
116256
  let content = "";
115977
116257
  try {
115978
- const data = await readFile3(validation.resolved, "utf-8");
116258
+ const data = await readFile4(validation.resolved, "utf-8");
115979
116259
  const limit = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
115980
116260
  if (data.length > limit) {
115981
116261
  content = `${data.slice(0, limit)}
@@ -116480,8 +116760,8 @@ function registerSecurityTools(registry, context) {
116480
116760
 
116481
116761
  // ../core/src/logger.ts
116482
116762
  await init_config();
116483
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, appendFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
116484
- import { join as join8 } from "path";
116763
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, appendFileSync, readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
116764
+ import { join as join9 } from "path";
116485
116765
  var SAFE_ID_PATTERN3 = /^[a-zA-Z0-9_-]+$/;
116486
116766
  function isValidId(id) {
116487
116767
  return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN3.test(id);
@@ -116493,14 +116773,14 @@ class Logger {
116493
116773
  sessionId;
116494
116774
  constructor(sessionId, basePath) {
116495
116775
  this.sessionId = sessionId;
116496
- this.logDir = join8(basePath || getConfigDir(), "logs");
116776
+ this.logDir = join9(basePath || getConfigDir(), "logs");
116497
116777
  this.ensureDir(this.logDir);
116498
116778
  const date = new Date().toISOString().split("T")[0];
116499
- this.logFile = join8(this.logDir, `${date}.log`);
116779
+ this.logFile = join9(this.logDir, `${date}.log`);
116500
116780
  }
116501
116781
  ensureDir(dir) {
116502
- if (!existsSync4(dir)) {
116503
- mkdirSync2(dir, { recursive: true });
116782
+ if (!existsSync5(dir)) {
116783
+ mkdirSync3(dir, { recursive: true });
116504
116784
  }
116505
116785
  }
116506
116786
  write(level, message, data) {
@@ -116532,12 +116812,12 @@ class Logger {
116532
116812
  this.write("error", message, data);
116533
116813
  }
116534
116814
  static readEntries(options) {
116535
- const logDir = join8(options?.basePath || getConfigDir(), "logs");
116536
- if (!existsSync4(logDir))
116815
+ const logDir = join9(options?.basePath || getConfigDir(), "logs");
116816
+ if (!existsSync5(logDir))
116537
116817
  return [];
116538
116818
  const levelOrder = { debug: 0, info: 1, warn: 2, error: 3 };
116539
116819
  const minLevel = options?.level ? levelOrder[options.level] : 0;
116540
- const files = readdirSync2(logDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.log$/.test(f)).sort((a, b) => b.localeCompare(a));
116820
+ const files = readdirSync3(logDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.log$/.test(f)).sort((a, b) => b.localeCompare(a));
116541
116821
  const entries = [];
116542
116822
  for (const file of files) {
116543
116823
  if (options?.since) {
@@ -116547,7 +116827,7 @@ class Logger {
116547
116827
  break;
116548
116828
  }
116549
116829
  try {
116550
- const content = readFileSync3(join8(logDir, file), "utf-8");
116830
+ const content = readFileSync3(join9(logDir, file), "utf-8");
116551
116831
  const lines = content.trim().split(`
116552
116832
  `).filter(Boolean);
116553
116833
  for (const line of lines) {
@@ -116570,10 +116850,10 @@ class Logger {
116570
116850
  return entries.slice(offset, offset + limit);
116571
116851
  }
116572
116852
  static listLogDates(basePath) {
116573
- const logDir = join8(basePath || getConfigDir(), "logs");
116574
- if (!existsSync4(logDir))
116853
+ const logDir = join9(basePath || getConfigDir(), "logs");
116854
+ if (!existsSync5(logDir))
116575
116855
  return [];
116576
- return readdirSync2(logDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.log$/.test(f)).map((f) => f.replace(".log", "")).sort((a, b) => b.localeCompare(a));
116856
+ return readdirSync3(logDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.log$/.test(f)).map((f) => f.replace(".log", "")).sort((a, b) => b.localeCompare(a));
116577
116857
  }
116578
116858
  }
116579
116859
 
@@ -116588,13 +116868,13 @@ class SessionStorage {
116588
116868
  this.sessionId = sessionId;
116589
116869
  const root = basePath || getConfigDir();
116590
116870
  const safeAssistantId = isValidId(assistantId) ? assistantId : null;
116591
- this.sessionsDir = safeAssistantId ? join8(root, "assistants", safeAssistantId, "sessions") : join8(root, "sessions");
116871
+ this.sessionsDir = safeAssistantId ? join9(root, "assistants", safeAssistantId, "sessions") : join9(root, "sessions");
116592
116872
  this.ensureDir(this.sessionsDir);
116593
- this.sessionFile = join8(this.sessionsDir, `${sessionId}.json`);
116873
+ this.sessionFile = join9(this.sessionsDir, `${sessionId}.json`);
116594
116874
  }
116595
116875
  ensureDir(dir) {
116596
- if (!existsSync4(dir)) {
116597
- mkdirSync2(dir, { recursive: true });
116876
+ if (!existsSync5(dir)) {
116877
+ mkdirSync3(dir, { recursive: true });
116598
116878
  }
116599
116879
  }
116600
116880
  save(data) {
@@ -116607,7 +116887,7 @@ class SessionStorage {
116607
116887
  }
116608
116888
  load() {
116609
116889
  try {
116610
- if (!existsSync4(this.sessionFile))
116890
+ if (!existsSync5(this.sessionFile))
116611
116891
  return null;
116612
116892
  return JSON.parse(readFileSync3(this.sessionFile, "utf-8"));
116613
116893
  } catch {
@@ -116616,8 +116896,8 @@ class SessionStorage {
116616
116896
  }
116617
116897
  static getActiveAssistantId() {
116618
116898
  try {
116619
- const activePath = join8(getConfigDir(), "active.json");
116620
- if (!existsSync4(activePath))
116899
+ const activePath = join9(getConfigDir(), "active.json");
116900
+ if (!existsSync5(activePath))
116621
116901
  return null;
116622
116902
  const raw = readFileSync3(activePath, "utf-8");
116623
116903
  const data = JSON.parse(raw);
@@ -116631,38 +116911,65 @@ class SessionStorage {
116631
116911
  }
116632
116912
  }
116633
116913
  static resolveSessionsDir(assistantId) {
116914
+ return SessionStorage.resolveSessionsDirWithAssistant(assistantId).dir;
116915
+ }
116916
+ static resolveSessionsDirWithAssistant(assistantId) {
116634
116917
  const root = getConfigDir();
116635
116918
  const safeAssistantId = isValidId(assistantId) ? assistantId : null;
116636
116919
  const resolvedId = safeAssistantId ?? SessionStorage.getActiveAssistantId();
116637
116920
  if (resolvedId && isValidId(resolvedId)) {
116638
- const assistantDir = join8(root, "assistants", resolvedId, "sessions");
116639
- if (existsSync4(assistantDir)) {
116640
- return assistantDir;
116921
+ const assistantDir = join9(root, "assistants", resolvedId, "sessions");
116922
+ if (existsSync5(assistantDir)) {
116923
+ return { dir: assistantDir, assistantId: resolvedId };
116641
116924
  }
116642
116925
  }
116643
- return join8(root, "sessions");
116926
+ return { dir: join9(root, "sessions"), assistantId: null };
116644
116927
  }
116645
- static listSessions(assistantId) {
116646
- const sessionsDir = SessionStorage.resolveSessionsDir(assistantId);
116647
- if (!existsSync4(sessionsDir))
116928
+ static readSessionsFromDir(sessionsDir, assistantId) {
116929
+ if (!existsSync5(sessionsDir))
116648
116930
  return [];
116649
116931
  const sessions = [];
116650
- const files = readdirSync2(sessionsDir);
116932
+ const files = readdirSync3(sessionsDir);
116651
116933
  for (const file of files) {
116652
116934
  if (!file.endsWith(".json"))
116653
116935
  continue;
116654
116936
  try {
116655
- const filePath = join8(sessionsDir, file);
116937
+ const filePath = join9(sessionsDir, file);
116656
116938
  const content = JSON.parse(readFileSync3(filePath, "utf-8"));
116657
116939
  sessions.push({
116658
116940
  id: file.replace(".json", ""),
116659
116941
  cwd: content.cwd,
116660
116942
  startedAt: content.startedAt,
116661
116943
  updatedAt: content.updatedAt,
116662
- messageCount: content.messages?.length || 0
116944
+ messageCount: content.messages?.length || 0,
116945
+ assistantId
116663
116946
  });
116664
116947
  } catch {}
116665
116948
  }
116949
+ return sessions;
116950
+ }
116951
+ static listSessions(assistantId) {
116952
+ const { dir, assistantId: resolvedAssistantId } = SessionStorage.resolveSessionsDirWithAssistant(assistantId);
116953
+ const sessions = SessionStorage.readSessionsFromDir(dir, resolvedAssistantId);
116954
+ return sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
116955
+ }
116956
+ static listAllSessions() {
116957
+ const root = getConfigDir();
116958
+ const sessions = [];
116959
+ const rootSessionsDir = join9(root, "sessions");
116960
+ sessions.push(...SessionStorage.readSessionsFromDir(rootSessionsDir, null));
116961
+ const assistantsDir = join9(root, "assistants");
116962
+ if (existsSync5(assistantsDir)) {
116963
+ const entries = readdirSync3(assistantsDir, { withFileTypes: true });
116964
+ for (const entry of entries) {
116965
+ if (!entry.isDirectory())
116966
+ continue;
116967
+ if (!isValidId(entry.name))
116968
+ continue;
116969
+ const assistantSessionsDir = join9(assistantsDir, entry.name, "sessions");
116970
+ sessions.push(...SessionStorage.readSessionsFromDir(assistantSessionsDir, entry.name));
116971
+ }
116972
+ }
116666
116973
  return sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
116667
116974
  }
116668
116975
  static getLatestSession(assistantId) {
@@ -116674,9 +116981,9 @@ class SessionStorage {
116674
116981
  return null;
116675
116982
  }
116676
116983
  const sessionsDir = SessionStorage.resolveSessionsDir(assistantId);
116677
- const sessionFile = join8(sessionsDir, `${sessionId}.json`);
116984
+ const sessionFile = join9(sessionsDir, `${sessionId}.json`);
116678
116985
  try {
116679
- if (!existsSync4(sessionFile))
116986
+ if (!existsSync5(sessionFile))
116680
116987
  return null;
116681
116988
  return JSON.parse(readFileSync3(sessionFile, "utf-8"));
116682
116989
  } catch {
@@ -116688,40 +116995,40 @@ function initAssistantsDir() {
116688
116995
  const baseDir = getConfigDir();
116689
116996
  const dirs = [
116690
116997
  baseDir,
116691
- join8(baseDir, "logs"),
116692
- join8(baseDir, "assistants"),
116693
- join8(baseDir, "shared", "skills"),
116694
- join8(baseDir, "commands"),
116695
- join8(baseDir, "temp"),
116696
- join8(baseDir, "heartbeats"),
116697
- join8(baseDir, "state"),
116698
- join8(baseDir, "energy"),
116699
- join8(baseDir, "migration")
116998
+ join9(baseDir, "logs"),
116999
+ join9(baseDir, "assistants"),
117000
+ join9(baseDir, "shared", "skills"),
117001
+ join9(baseDir, "commands"),
117002
+ join9(baseDir, "temp"),
117003
+ join9(baseDir, "heartbeats"),
117004
+ join9(baseDir, "state"),
117005
+ join9(baseDir, "energy"),
117006
+ join9(baseDir, "migration")
116700
117007
  ];
116701
117008
  for (const dir of dirs) {
116702
- if (!existsSync4(dir)) {
116703
- mkdirSync2(dir, { recursive: true });
117009
+ if (!existsSync5(dir)) {
117010
+ mkdirSync3(dir, { recursive: true });
116704
117011
  }
116705
117012
  }
116706
117013
  }
116707
117014
 
116708
117015
  // ../core/src/hooks/logger.ts
116709
117016
  await init_config();
116710
- import { join as join9 } from "path";
116711
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, appendFileSync as appendFileSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
117017
+ import { join as join10 } from "path";
117018
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, appendFileSync as appendFileSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
116712
117019
  var MAX_ENTRIES = 1000;
116713
117020
  function getLogPath() {
116714
- return join9(getConfigDir(), "logs", "hooks.jsonl");
117021
+ return join10(getConfigDir(), "logs", "hooks.jsonl");
116715
117022
  }
116716
117023
  function ensureLogsDir() {
116717
- const logsDir = join9(getConfigDir(), "logs");
116718
- if (!existsSync5(logsDir)) {
116719
- mkdirSync3(logsDir, { recursive: true });
117024
+ const logsDir = join10(getConfigDir(), "logs");
117025
+ if (!existsSync6(logsDir)) {
117026
+ mkdirSync4(logsDir, { recursive: true });
116720
117027
  }
116721
117028
  }
116722
117029
  function rotateIfNeeded() {
116723
117030
  const logPath = getLogPath();
116724
- if (!existsSync5(logPath))
117031
+ if (!existsSync6(logPath))
116725
117032
  return;
116726
117033
  try {
116727
117034
  const content = readFileSync4(logPath, "utf-8");
@@ -116779,7 +117086,7 @@ class HookLogger {
116779
117086
  }
116780
117087
  static getHistory(limit = 50, hookId) {
116781
117088
  const logPath = getLogPath();
116782
- if (!existsSync5(logPath))
117089
+ if (!existsSync6(logPath))
116783
117090
  return [];
116784
117091
  try {
116785
117092
  const content = readFileSync4(logPath, "utf-8");
@@ -116801,7 +117108,7 @@ class HookLogger {
116801
117108
  }
116802
117109
  static clearHistory() {
116803
117110
  const logPath = getLogPath();
116804
- if (existsSync5(logPath)) {
117111
+ if (existsSync6(logPath)) {
116805
117112
  writeFileSync3(logPath, "", "utf-8");
116806
117113
  }
116807
117114
  }
@@ -117172,22 +117479,22 @@ function registerLogsTools(registry, context) {
117172
117479
 
117173
117480
  // ../core/src/sessions/verification.ts
117174
117481
  init_src2();
117175
- import { join as join10 } from "path";
117176
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync as readdirSync3, unlinkSync } from "fs";
117482
+ import { join as join11 } from "path";
117483
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync as readdirSync4, unlinkSync } from "fs";
117177
117484
 
117178
117485
  class VerificationSessionStore {
117179
117486
  basePath;
117180
117487
  maxSessions;
117181
117488
  lastTimestamp;
117182
117489
  constructor(basePath, maxSessions = 100) {
117183
- this.basePath = join10(basePath, "verifications");
117490
+ this.basePath = join11(basePath, "verifications");
117184
117491
  this.maxSessions = maxSessions;
117185
117492
  this.lastTimestamp = 0;
117186
117493
  this.ensureDirectory();
117187
117494
  }
117188
117495
  ensureDirectory() {
117189
- if (!existsSync6(this.basePath)) {
117190
- mkdirSync4(this.basePath, { recursive: true });
117496
+ if (!existsSync7(this.basePath)) {
117497
+ mkdirSync5(this.basePath, { recursive: true });
117191
117498
  }
117192
117499
  }
117193
117500
  create(parentSessionId, goals, verificationResult) {
@@ -117212,12 +117519,12 @@ class VerificationSessionStore {
117212
117519
  return session;
117213
117520
  }
117214
117521
  save(session) {
117215
- const filePath = join10(this.basePath, `${session.id}.json`);
117522
+ const filePath = join11(this.basePath, `${session.id}.json`);
117216
117523
  writeFileSync4(filePath, JSON.stringify(session, null, 2));
117217
117524
  }
117218
117525
  get(id) {
117219
- const filePath = join10(this.basePath, `${id}.json`);
117220
- if (!existsSync6(filePath)) {
117526
+ const filePath = join11(this.basePath, `${id}.json`);
117527
+ if (!existsSync7(filePath)) {
117221
117528
  return null;
117222
117529
  }
117223
117530
  try {
@@ -117232,7 +117539,7 @@ class VerificationSessionStore {
117232
117539
  const files = this.listFiles();
117233
117540
  for (const file of files) {
117234
117541
  try {
117235
- const content = readFileSync5(join10(this.basePath, file), "utf-8");
117542
+ const content = readFileSync5(join11(this.basePath, file), "utf-8");
117236
117543
  const session = JSON.parse(content);
117237
117544
  if (session.parentSessionId === parentSessionId) {
117238
117545
  sessions.push(session);
@@ -117248,7 +117555,7 @@ class VerificationSessionStore {
117248
117555
  const files = this.listFiles();
117249
117556
  for (const file of files) {
117250
117557
  try {
117251
- const content = readFileSync5(join10(this.basePath, file), "utf-8");
117558
+ const content = readFileSync5(join11(this.basePath, file), "utf-8");
117252
117559
  const session = JSON.parse(content);
117253
117560
  sessions.push(session);
117254
117561
  } catch {
@@ -117265,10 +117572,10 @@ class VerificationSessionStore {
117265
117572
  this.save(session);
117266
117573
  }
117267
117574
  listFiles() {
117268
- if (!existsSync6(this.basePath)) {
117575
+ if (!existsSync7(this.basePath)) {
117269
117576
  return [];
117270
117577
  }
117271
- return readdirSync3(this.basePath).filter((f) => f.endsWith(".json"));
117578
+ return readdirSync4(this.basePath).filter((f) => f.endsWith(".json"));
117272
117579
  }
117273
117580
  pruneOldSessions() {
117274
117581
  const files = this.listFiles();
@@ -117278,7 +117585,7 @@ class VerificationSessionStore {
117278
117585
  const sessions = [];
117279
117586
  for (const file of files) {
117280
117587
  try {
117281
- const content = readFileSync5(join10(this.basePath, file), "utf-8");
117588
+ const content = readFileSync5(join11(this.basePath, file), "utf-8");
117282
117589
  const session = JSON.parse(content);
117283
117590
  sessions.push({
117284
117591
  file,
@@ -117292,7 +117599,7 @@ class VerificationSessionStore {
117292
117599
  const toRemove = sessions.slice(0, sessions.length - this.maxSessions);
117293
117600
  for (const item of toRemove) {
117294
117601
  try {
117295
- unlinkSync(join10(this.basePath, item.file));
117602
+ unlinkSync(join11(this.basePath, item.file));
117296
117603
  } catch {
117297
117604
  continue;
117298
117605
  }
@@ -117302,7 +117609,7 @@ class VerificationSessionStore {
117302
117609
  const files = this.listFiles();
117303
117610
  for (const file of files) {
117304
117611
  try {
117305
- unlinkSync(join10(this.basePath, file));
117612
+ unlinkSync(join11(this.basePath, file));
117306
117613
  } catch {
117307
117614
  continue;
117308
117615
  }
@@ -117364,7 +117671,7 @@ class HookLoader {
117364
117671
  // ../core/src/hooks/executor.ts
117365
117672
  init_src2();
117366
117673
  await init_runtime();
117367
- import { existsSync as existsSync7 } from "fs";
117674
+ import { existsSync as existsSync8 } from "fs";
117368
117675
 
117369
117676
  // ../core/src/hooks/background.ts
117370
117677
  class BackgroundProcessManager {
@@ -117578,7 +117885,7 @@ class HookExecutor {
117578
117885
  }
117579
117886
  try {
117580
117887
  const runtime = getRuntime();
117581
- const cwd = input.cwd && existsSync7(input.cwd) ? input.cwd : process.cwd();
117888
+ const cwd = input.cwd && existsSync8(input.cwd) ? input.cwd : process.cwd();
117582
117889
  const isWindows = process.platform === "win32";
117583
117890
  const shellBinary = isWindows ? "cmd" : runtime.which("bash") || "sh";
117584
117891
  const shellArgs = isWindows ? ["/c", hook.command] : ["-lc", hook.command];
@@ -117631,7 +117938,7 @@ class HookExecutor {
117631
117938
  return null;
117632
117939
  try {
117633
117940
  const runtime = getRuntime();
117634
- const cwd = input.cwd && existsSync7(input.cwd) ? input.cwd : process.cwd();
117941
+ const cwd = input.cwd && existsSync8(input.cwd) ? input.cwd : process.cwd();
117635
117942
  const isWindows = process.platform === "win32";
117636
117943
  const shellBinary = isWindows ? "cmd" : runtime.which("bash") || "sh";
117637
117944
  const shellArgs = isWindows ? ["/c", hook.command] : ["-lc", hook.command];
@@ -118200,8 +118507,8 @@ function createScopeVerificationHook() {
118200
118507
  }
118201
118508
  // ../core/src/hooks/store.ts
118202
118509
  await init_config();
118203
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
118204
- import { join as join11, dirname as dirname4 } from "path";
118510
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
118511
+ import { join as join12, dirname as dirname5 } from "path";
118205
118512
  import { createHash as createHash2 } from "crypto";
118206
118513
  function generateHookId2(event, hook) {
118207
118514
  const content = hook.command || hook.prompt || "";
@@ -118228,16 +118535,16 @@ class HookStore {
118228
118535
  getFilePath(location) {
118229
118536
  switch (location) {
118230
118537
  case "user":
118231
- return join11(getConfigDir(), "hooks.json");
118538
+ return join12(getConfigDir(), "hooks.json");
118232
118539
  case "project":
118233
- return join11(this.cwd, ".assistants", "hooks.json");
118540
+ return join12(this.cwd, ".assistants", "hooks.json");
118234
118541
  case "local":
118235
- return join11(this.cwd, ".assistants", "hooks.local.json");
118542
+ return join12(this.cwd, ".assistants", "hooks.local.json");
118236
118543
  }
118237
118544
  }
118238
118545
  loadFrom(location) {
118239
118546
  const filePath = this.getFilePath(location);
118240
- if (!existsSync8(filePath)) {
118547
+ if (!existsSync9(filePath)) {
118241
118548
  return {};
118242
118549
  }
118243
118550
  try {
@@ -118252,9 +118559,9 @@ class HookStore {
118252
118559
  }
118253
118560
  save(location, config) {
118254
118561
  const filePath = this.getFilePath(location);
118255
- const dir = dirname4(filePath);
118256
- if (!existsSync8(dir)) {
118257
- mkdirSync5(dir, { recursive: true });
118562
+ const dir = dirname5(filePath);
118563
+ if (!existsSync9(dir)) {
118564
+ mkdirSync6(dir, { recursive: true });
118258
118565
  }
118259
118566
  ensureHookIds(config);
118260
118567
  writeFileSync5(filePath, JSON.stringify({ hooks: config }, null, 2), "utf-8");
@@ -118394,7 +118701,7 @@ class HookStore {
118394
118701
  // ../core/src/hooks/tester.ts
118395
118702
  init_src2();
118396
118703
  await init_runtime();
118397
- import { existsSync as existsSync9 } from "fs";
118704
+ import { existsSync as existsSync10 } from "fs";
118398
118705
  var sampleInputs = {
118399
118706
  SessionStart: {
118400
118707
  source: "test"
@@ -118518,7 +118825,7 @@ class HookTester {
118518
118825
  }
118519
118826
  async executeCommand(command, input, timeout) {
118520
118827
  const runtime = getRuntime();
118521
- const cwd = input.cwd && existsSync9(input.cwd) ? input.cwd : process.cwd();
118828
+ const cwd = input.cwd && existsSync10(input.cwd) ? input.cwd : process.cwd();
118522
118829
  const isWindows = process.platform === "win32";
118523
118830
  const shellBinary = isWindows ? "cmd" : runtime.which("bash") || "sh";
118524
118831
  const shellArgs = isWindows ? ["/c", command] : ["-lc", command];
@@ -119625,7 +119932,7 @@ await __promiseAll([
119625
119932
  init_config(),
119626
119933
  init_runtime()
119627
119934
  ]);
119628
- import { join as join12, resolve as resolve4, dirname as dirname5, sep } from "path";
119935
+ import { join as join13, resolve as resolve4, dirname as dirname6, sep } from "path";
119629
119936
  import { homedir as homedir7 } from "os";
119630
119937
  import { mkdir as mkdir5 } from "fs/promises";
119631
119938
 
@@ -119791,7 +120098,7 @@ function isWithinPath2(target, base) {
119791
120098
  var currentSessionId = "default";
119792
120099
  function getScriptsFolder(cwd, sessionId) {
119793
120100
  const resolvedSessionId = sessionId || currentSessionId;
119794
- return join12(getProjectConfigDir(cwd), "scripts", resolvedSessionId);
120101
+ return join13(getProjectConfigDir(cwd), "scripts", resolvedSessionId);
119795
120102
  }
119796
120103
  function isInScriptsFolder(path, cwd, sessionId) {
119797
120104
  const scriptsFolder = resolve4(getScriptsFolder(cwd, sessionId));
@@ -120000,7 +120307,7 @@ class FilesystemTools {
120000
120307
  });
120001
120308
  }
120002
120309
  const sanitizedFilename = filename.replace(/\.\.[/\\]/g, "").replace(/\.\./g, "").replace(/^[/\\]+/, "");
120003
- const path = join12(scriptsFolder, sanitizedFilename);
120310
+ const path = join13(scriptsFolder, sanitizedFilename);
120004
120311
  if (!isInScriptsFolder(path, baseCwd, input.sessionId)) {
120005
120312
  throw new ToolExecutionError(`Cannot write outside scripts folder. Files are saved to ${scriptsFolder}`, {
120006
120313
  toolName: "write",
@@ -120043,7 +120350,7 @@ class FilesystemTools {
120043
120350
  retryable: false
120044
120351
  });
120045
120352
  }
120046
- const dir = dirname5(validated.resolved);
120353
+ const dir = dirname6(validated.resolved);
120047
120354
  await mkdir5(dir, { recursive: true });
120048
120355
  const runtime = getRuntime();
120049
120356
  await runtime.write(validated.resolved, content);
@@ -120251,7 +120558,7 @@ class FilesystemTools {
120251
120558
  if (file.includes(".Trash") || file.includes(".Spotlight-V100") || file.includes(".fseventsd")) {
120252
120559
  continue;
120253
120560
  }
120254
- const filePath = join12(validated.resolved, file);
120561
+ const filePath = join13(validated.resolved, file);
120255
120562
  try {
120256
120563
  const content = await runtime.file(filePath).text();
120257
120564
  const lines = content.split(`
@@ -121094,8 +121401,8 @@ function isPrivateIPv42(octets) {
121094
121401
  // ../core/src/tools/feedback.ts
121095
121402
  init_src2();
121096
121403
  await init_config();
121097
- import { join as join13 } from "path";
121098
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
121404
+ import { join as join14 } from "path";
121405
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
121099
121406
  function normalizeTags(value) {
121100
121407
  if (Array.isArray(value)) {
121101
121408
  const tags = value.map((t) => String(t).trim()).filter(Boolean);
@@ -121109,16 +121416,16 @@ function normalizeTags(value) {
121109
121416
  }
121110
121417
  function resolveFeedbackDir(cwd) {
121111
121418
  const baseCwd = cwd && cwd.trim().length > 0 ? cwd : process.cwd();
121112
- const projectConfigDir = join13(baseCwd, ".assistants");
121113
- if (existsSync10(projectConfigDir)) {
121114
- return join13(projectConfigDir, "feedback");
121419
+ const projectConfigDir = join14(baseCwd, ".assistants");
121420
+ if (existsSync11(projectConfigDir)) {
121421
+ return join14(projectConfigDir, "feedback");
121115
121422
  }
121116
- return join13(getConfigDir(), "feedback");
121423
+ return join14(getConfigDir(), "feedback");
121117
121424
  }
121118
121425
  function saveFeedbackEntry(entry, cwd) {
121119
121426
  const feedbackDir = resolveFeedbackDir(cwd);
121120
- mkdirSync6(feedbackDir, { recursive: true });
121121
- const path = join13(feedbackDir, `${entry.id}.json`);
121427
+ mkdirSync7(feedbackDir, { recursive: true });
121428
+ const path = join14(feedbackDir, `${entry.id}.json`);
121122
121429
  writeFileSync6(path, JSON.stringify(entry, null, 2));
121123
121430
  return { path };
121124
121431
  }
@@ -121218,15 +121525,15 @@ init_src2();
121218
121525
 
121219
121526
  // ../core/src/scheduler/store.ts
121220
121527
  await init_config();
121221
- import { join as join14 } from "path";
121222
- import { mkdir as mkdir6, readdir as readdir2, readFile as readFile4, unlink as unlink3, writeFile as writeFile3, open as open2 } from "fs/promises";
121528
+ import { join as join15 } from "path";
121529
+ import { mkdir as mkdir6, readdir as readdir2, readFile as readFile5, unlink as unlink3, writeFile as writeFile3, open as open2 } from "fs/promises";
121223
121530
  var DEFAULT_LOCK_TTL_MS = 10 * 60 * 1000;
121224
121531
  var SAFE_ID_PATTERN4 = /^[a-zA-Z0-9_-]+$/;
121225
121532
  function schedulesDir(cwd) {
121226
- return join14(getProjectConfigDir(cwd), "schedules");
121533
+ return join15(getProjectConfigDir(cwd), "schedules");
121227
121534
  }
121228
121535
  function locksDir(cwd) {
121229
- return join14(schedulesDir(cwd), "locks");
121536
+ return join15(schedulesDir(cwd), "locks");
121230
121537
  }
121231
121538
  function isSafeId3(id) {
121232
121539
  return SAFE_ID_PATTERN4.test(id);
@@ -121234,10 +121541,10 @@ function isSafeId3(id) {
121234
121541
  function schedulePath(cwd, id) {
121235
121542
  if (!isSafeId3(id))
121236
121543
  return null;
121237
- return join14(schedulesDir(cwd), `${id}.json`);
121544
+ return join15(schedulesDir(cwd), `${id}.json`);
121238
121545
  }
121239
121546
  function lockPath(cwd, id) {
121240
- return join14(locksDir(cwd), `${id}.lock.json`);
121547
+ return join15(locksDir(cwd), `${id}.lock.json`);
121241
121548
  }
121242
121549
  async function ensureDirs(cwd) {
121243
121550
  await mkdir6(schedulesDir(cwd), { recursive: true });
@@ -121252,7 +121559,7 @@ async function listSchedules(cwd, options) {
121252
121559
  if (!file.endsWith(".json"))
121253
121560
  continue;
121254
121561
  try {
121255
- const raw = await readFile4(join14(dir, file), "utf-8");
121562
+ const raw = await readFile5(join15(dir, file), "utf-8");
121256
121563
  const parsed = JSON.parse(raw);
121257
121564
  if (parsed?.id)
121258
121565
  schedules.push(parsed);
@@ -121279,7 +121586,7 @@ async function getSchedule(cwd, id) {
121279
121586
  const path = schedulePath(cwd, id);
121280
121587
  if (!path)
121281
121588
  return null;
121282
- const raw = await readFile4(path, "utf-8");
121589
+ const raw = await readFile5(path, "utf-8");
121283
121590
  return JSON.parse(raw);
121284
121591
  } catch {
121285
121592
  return null;
@@ -121360,7 +121667,7 @@ async function updateSchedule(cwd, id, updater) {
121360
121667
  const path = schedulePath(cwd, id);
121361
121668
  if (!path)
121362
121669
  return null;
121363
- const raw = await readFile4(path, "utf-8");
121670
+ const raw = await readFile5(path, "utf-8");
121364
121671
  const schedule = JSON.parse(raw);
121365
121672
  const updated = updater(schedule);
121366
121673
  await saveSchedule(cwd, updated);
@@ -121382,7 +121689,7 @@ async function acquireScheduleLock(cwd, id, ownerId, ttlMs = DEFAULT_LOCK_TTL_MS
121382
121689
  return true;
121383
121690
  } catch {
121384
121691
  try {
121385
- const raw = await readFile4(path, "utf-8");
121692
+ const raw = await readFile5(path, "utf-8");
121386
121693
  const lock = JSON.parse(raw);
121387
121694
  const updatedAt = lock?.updatedAt || lock?.createdAt || 0;
121388
121695
  const ttl = lock?.ttlMs ?? ttlMs;
@@ -121408,7 +121715,7 @@ async function releaseScheduleLock(cwd, id, ownerId) {
121408
121715
  return;
121409
121716
  const path = lockPath(cwd, id);
121410
121717
  try {
121411
- const raw = await readFile4(path, "utf-8");
121718
+ const raw = await readFile5(path, "utf-8");
121412
121719
  const lock = JSON.parse(raw);
121413
121720
  if (lock?.ownerId === ownerId) {
121414
121721
  await unlink3(path);
@@ -121420,7 +121727,7 @@ async function refreshScheduleLock(cwd, id, ownerId) {
121420
121727
  return;
121421
121728
  const path = lockPath(cwd, id);
121422
121729
  try {
121423
- const raw = await readFile4(path, "utf-8");
121730
+ const raw = await readFile5(path, "utf-8");
121424
121731
  const lock = JSON.parse(raw);
121425
121732
  if (lock?.ownerId === ownerId) {
121426
121733
  const updated = { ...lock, updatedAt: Date.now() };
@@ -121433,7 +121740,7 @@ async function readSchedule(cwd, id) {
121433
121740
  const path = schedulePath(cwd, id);
121434
121741
  if (!path)
121435
121742
  return null;
121436
- const raw = await readFile4(path, "utf-8");
121743
+ const raw = await readFile5(path, "utf-8");
121437
121744
  const schedule = JSON.parse(raw);
121438
121745
  if (!schedule?.id)
121439
121746
  return null;
@@ -121772,9 +122079,9 @@ class SchedulerTool {
121772
122079
  // ../core/src/tools/image.ts
121773
122080
  init_src2();
121774
122081
  await init_runtime();
121775
- import { existsSync as existsSync11, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
122082
+ import { existsSync as existsSync12, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
121776
122083
  import { tmpdir } from "os";
121777
- import { join as join15 } from "path";
122084
+ import { join as join16 } from "path";
121778
122085
  import { homedir as homedir8 } from "os";
121779
122086
  var FETCH_TIMEOUT_MS = 30000;
121780
122087
  var MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
@@ -121793,7 +122100,7 @@ async function getViuPath() {
121793
122100
  const homeDir = envHome && envHome.trim().length > 0 ? envHome : homedir8();
121794
122101
  const locations = [
121795
122102
  "viu",
121796
- join15(homeDir, ".cargo", "bin", "viu"),
122103
+ join16(homeDir, ".cargo", "bin", "viu"),
121797
122104
  "/usr/local/bin/viu",
121798
122105
  "/opt/homebrew/bin/viu"
121799
122106
  ];
@@ -121898,7 +122205,7 @@ class ImageDisplayTool {
121898
122205
  offset += chunk.length;
121899
122206
  }
121900
122207
  const ext = contentType.split("/")[1]?.split(";")[0] || "png";
121901
- tempFile = join15(tmpdir(), `assistants-image-${generateId()}.${ext}`);
122208
+ tempFile = join16(tmpdir(), `assistants-image-${generateId()}.${ext}`);
121902
122209
  writeFileSync7(tempFile, buffer);
121903
122210
  localPath = tempFile;
121904
122211
  } catch (error) {
@@ -121908,7 +122215,7 @@ class ImageDisplayTool {
121908
122215
  return `Error: Failed to fetch image: ${error instanceof Error ? error.message : String(error)}`;
121909
122216
  }
121910
122217
  }
121911
- if (!existsSync11(localPath)) {
122218
+ if (!existsSync12(localPath)) {
121912
122219
  return `Error: Image file not found: ${localPath}`;
121913
122220
  }
121914
122221
  try {
@@ -121936,7 +122243,7 @@ class ImageDisplayTool {
121936
122243
  } catch (error) {
121937
122244
  return `Error: ${error instanceof Error ? error.message : String(error)}`;
121938
122245
  } finally {
121939
- if (tempFile && existsSync11(tempFile)) {
122246
+ if (tempFile && existsSync12(tempFile)) {
121940
122247
  try {
121941
122248
  unlinkSync2(tempFile);
121942
122249
  } catch {}
@@ -121956,7 +122263,7 @@ init_errors();
121956
122263
 
121957
122264
  // ../core/src/skills/create.ts
121958
122265
  await init_config();
121959
- import { join as join16, dirname as dirname6 } from "path";
122266
+ import { join as join17, dirname as dirname7 } from "path";
121960
122267
  import { mkdir as mkdir7, stat, writeFile as writeFile4, rm } from "fs/promises";
121961
122268
  function slugify(input) {
121962
122269
  return input.trim().toLowerCase().replace(/[^a-z0-9\s_-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -122013,9 +122320,9 @@ function buildDefaultContent() {
122013
122320
  }
122014
122321
  function resolveSkillRoot(scope, cwd) {
122015
122322
  if (scope === "global") {
122016
- return join16(getConfigDir(), "shared", "skills");
122323
+ return join17(getConfigDir(), "shared", "skills");
122017
122324
  }
122018
- return join16(cwd, ".assistants", "skills");
122325
+ return join17(cwd, ".assistants", "skills");
122019
122326
  }
122020
122327
  async function pathExists(path) {
122021
122328
  try {
@@ -122029,8 +122336,8 @@ async function createSkill(options) {
122029
122336
  const scope = options.scope ?? "project";
122030
122337
  const { skillName, dirName } = normalizeName2(options.name);
122031
122338
  const root = resolveSkillRoot(scope, options.cwd);
122032
- const directory = join16(root, dirName);
122033
- const filePath = join16(directory, "SKILL.md");
122339
+ const directory = join17(root, dirName);
122340
+ const filePath = join17(directory, "SKILL.md");
122034
122341
  if (!options.overwrite && await pathExists(filePath)) {
122035
122342
  throw new Error(`Skill already exists at ${filePath}`);
122036
122343
  }
@@ -122053,7 +122360,7 @@ ${content}
122053
122360
  // ../core/src/skills/executor.ts
122054
122361
  init_src2();
122055
122362
  await init_runtime();
122056
- import { dirname as dirname7 } from "path";
122363
+ import { dirname as dirname8 } from "path";
122057
122364
 
122058
122365
  class SkillExecutor {
122059
122366
  constructor() {}
@@ -122074,7 +122381,7 @@ ARGUMENTS: ${args.join(" ")}`;
122074
122381
  if (matches.length === 0) {
122075
122382
  return content;
122076
122383
  }
122077
- const skillDir = dirname7(skillFilePath);
122384
+ const skillDir = dirname8(skillFilePath);
122078
122385
  let result = content;
122079
122386
  const runtime = getRuntime();
122080
122387
  for (const match of matches) {
@@ -122697,9 +123004,9 @@ Respond with ALLOW or DENY on the first line, followed by a short reason.`,
122697
123004
  // ../core/src/skills/loader.ts
122698
123005
  init_src2();
122699
123006
  var import_fast_glob = __toESM(require_out4(), 1);
122700
- import { join as join17, basename as basename3, dirname as dirname8 } from "path";
123007
+ import { join as join18, basename as basename3, dirname as dirname9 } from "path";
122701
123008
  import { homedir as homedir9 } from "os";
122702
- import { readFile as readFile5, stat as stat2 } from "fs/promises";
123009
+ import { readFile as readFile6, stat as stat2 } from "fs/promises";
122703
123010
 
122704
123011
  class SkillLoader {
122705
123012
  skills = new Map;
@@ -122707,9 +123014,9 @@ class SkillLoader {
122707
123014
  const includeContent = options.includeContent ?? true;
122708
123015
  const envHome = process.env.HOME || process.env.USERPROFILE;
122709
123016
  const userHome = envHome && envHome.trim().length > 0 ? envHome : homedir9();
122710
- const userSkillsDir = join17(userHome, ".assistants", "shared", "skills");
123017
+ const userSkillsDir = join18(userHome, ".assistants", "shared", "skills");
122711
123018
  await this.loadFromDirectory(userSkillsDir, { includeContent });
122712
- const projectSkillsDir = join17(projectDir, ".assistants", "skills");
123019
+ const projectSkillsDir = join18(projectDir, ".assistants", "skills");
122713
123020
  await this.loadFromDirectory(projectSkillsDir, { includeContent });
122714
123021
  const nestedFiles = await import_fast_glob.default("**/.assistants/skills/*/SKILL.md", {
122715
123022
  cwd: projectDir,
@@ -122717,7 +123024,7 @@ class SkillLoader {
122717
123024
  ignore: ["**/node_modules/**"]
122718
123025
  });
122719
123026
  for (const file of nestedFiles) {
122720
- await this.loadSkillFile(join17(projectDir, file), { includeContent });
123027
+ await this.loadSkillFile(join18(projectDir, file), { includeContent });
122721
123028
  }
122722
123029
  }
122723
123030
  async loadFromDirectory(dir, options = {}) {
@@ -122733,13 +123040,13 @@ class SkillLoader {
122733
123040
  const filesToLoad = [];
122734
123041
  const skillPrefixFiles = await import_fast_glob.default("skill-*/SKILL.md", { cwd: dir });
122735
123042
  for (const file of skillPrefixFiles) {
122736
- filesToLoad.push(join17(dir, file));
123043
+ filesToLoad.push(join18(dir, file));
122737
123044
  }
122738
123045
  const regularFiles = await import_fast_glob.default("*/SKILL.md", { cwd: dir });
122739
123046
  for (const file of regularFiles) {
122740
123047
  const dirName = file.split(/[\\/]/)[0];
122741
123048
  if (!dirName.startsWith("skill-")) {
122742
- filesToLoad.push(join17(dir, file));
123049
+ filesToLoad.push(join18(dir, file));
122743
123050
  }
122744
123051
  }
122745
123052
  const loadTasks = [];
@@ -122751,10 +123058,10 @@ class SkillLoader {
122751
123058
  }
122752
123059
  async loadSkillFile(filePath, options = {}) {
122753
123060
  try {
122754
- const content = await readFile5(filePath, "utf-8");
123061
+ const content = await readFile6(filePath, "utf-8");
122755
123062
  const { frontmatter, content: markdownContent } = parseFrontmatter(content);
122756
123063
  const includeContent = options.includeContent ?? true;
122757
- const dirName = basename3(dirname8(filePath));
123064
+ const dirName = basename3(dirname9(filePath));
122758
123065
  const name = frontmatter.name || dirName;
122759
123066
  let description = frontmatter.description || "";
122760
123067
  if (!description && markdownContent) {
@@ -122852,8 +123159,8 @@ class SkillLoader {
122852
123159
  }
122853
123160
  // ../core/src/commands/loader.ts
122854
123161
  await init_runtime();
122855
- import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
122856
- import { join as join18, basename as basename4, extname as extname2 } from "path";
123162
+ import { existsSync as existsSync13, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
123163
+ import { join as join19, basename as basename4, extname as extname2 } from "path";
122857
123164
  import { homedir as homedir10 } from "os";
122858
123165
 
122859
123166
  class CommandLoader {
@@ -122866,17 +123173,17 @@ class CommandLoader {
122866
123173
  this.commands.clear();
122867
123174
  const envHome = process.env.HOME || process.env.USERPROFILE;
122868
123175
  const homeDir = envHome && envHome.trim().length > 0 ? envHome : homedir10();
122869
- const globalDir = join18(homeDir, ".assistants", "commands");
123176
+ const globalDir = join19(homeDir, ".assistants", "commands");
122870
123177
  await this.loadFromDirectory(globalDir, "global");
122871
- const projectDir = join18(this.cwd, ".assistants", "commands");
123178
+ const projectDir = join19(this.cwd, ".assistants", "commands");
122872
123179
  await this.loadFromDirectory(projectDir, "project");
122873
123180
  }
122874
123181
  async loadFromDirectory(dir, source, prefix = "") {
122875
- if (!existsSync12(dir))
123182
+ if (!existsSync13(dir))
122876
123183
  return;
122877
- const entries = readdirSync4(dir);
123184
+ const entries = readdirSync5(dir);
122878
123185
  for (const entry of entries) {
122879
- const fullPath = join18(dir, entry);
123186
+ const fullPath = join19(dir, entry);
122880
123187
  const stat3 = statSync2(fullPath);
122881
123188
  if (stat3.isDirectory()) {
122882
123189
  const newPrefix = prefix ? `${prefix}:${entry}` : entry;
@@ -123150,11 +123457,11 @@ ${truncatedStderr}`;
123150
123457
  }
123151
123458
  // ../core/src/commands/builtin.ts
123152
123459
  await init_runtime();
123153
- import { join as join25 } from "path";
123460
+ import { join as join26 } from "path";
123154
123461
  import { homedir as homedir14, platform as platform2, release, arch as arch2 } from "os";
123155
- init_src2();
123156
123462
  await init_config();
123157
- import { existsSync as existsSync17, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
123463
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
123464
+ init_src2();
123158
123465
 
123159
123466
  // ../core/src/scheduler/format.ts
123160
123467
  function formatRelativeTime(timestamp, now2 = Date.now()) {
@@ -123184,11 +123491,11 @@ init_src2();
123184
123491
 
123185
123492
  // ../core/src/jobs/job-store.ts
123186
123493
  await init_config();
123187
- import { join as join19 } from "path";
123188
- import { mkdir as mkdir8, readdir as readdir3, readFile as readFile6, unlink as unlink4, writeFile as writeFile5 } from "fs/promises";
123494
+ import { join as join20 } from "path";
123495
+ import { mkdir as mkdir8, readdir as readdir3, readFile as readFile7, unlink as unlink4, writeFile as writeFile5 } from "fs/promises";
123189
123496
  var SAFE_ID_PATTERN5 = /^[a-zA-Z0-9_-]+$/;
123190
123497
  function jobsDir() {
123191
- return join19(getConfigDir(), "jobs");
123498
+ return join20(getConfigDir(), "jobs");
123192
123499
  }
123193
123500
  function isSafeId4(id) {
123194
123501
  return SAFE_ID_PATTERN5.test(id);
@@ -123196,7 +123503,7 @@ function isSafeId4(id) {
123196
123503
  function jobPath(id) {
123197
123504
  if (!isSafeId4(id))
123198
123505
  return null;
123199
- return join19(jobsDir(), `${id}.json`);
123506
+ return join20(jobsDir(), `${id}.json`);
123200
123507
  }
123201
123508
  async function ensureDir() {
123202
123509
  await mkdir8(jobsDir(), { recursive: true });
@@ -123214,7 +123521,7 @@ async function readJob(id) {
123214
123521
  const path = jobPath(id);
123215
123522
  if (!path)
123216
123523
  return null;
123217
- const raw = await readFile6(path, "utf-8");
123524
+ const raw = await readFile7(path, "utf-8");
123218
123525
  const job = JSON.parse(raw);
123219
123526
  if (!job?.id)
123220
123527
  return null;
@@ -123243,7 +123550,7 @@ async function listJobs() {
123243
123550
  if (!file.endsWith(".json"))
123244
123551
  continue;
123245
123552
  try {
123246
- const raw = await readFile6(join19(dir, file), "utf-8");
123553
+ const raw = await readFile7(join20(dir, file), "utf-8");
123247
123554
  const job = JSON.parse(raw);
123248
123555
  if (job?.id)
123249
123556
  jobs.push(job);
@@ -123830,15 +124137,15 @@ var PRIORITY_ORDER = {
123830
124137
  };
123831
124138
  // ../core/src/tasks/store.ts
123832
124139
  init_src2();
123833
- import { join as join20 } from "path";
123834
- import { mkdir as mkdir9, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
124140
+ import { join as join21 } from "path";
124141
+ import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
123835
124142
  var TASKS_DIR = ".assistants/tasks";
123836
124143
  var TASKS_FILE = "tasks.json";
123837
124144
  function tasksDir(cwd) {
123838
- return join20(cwd, TASKS_DIR);
124145
+ return join21(cwd, TASKS_DIR);
123839
124146
  }
123840
124147
  function tasksPath(cwd) {
123841
- return join20(tasksDir(cwd), TASKS_FILE);
124148
+ return join21(tasksDir(cwd), TASKS_FILE);
123842
124149
  }
123843
124150
  async function ensureTasksDir(cwd) {
123844
124151
  await mkdir9(tasksDir(cwd), { recursive: true });
@@ -123852,7 +124159,7 @@ function defaultStoreData() {
123852
124159
  }
123853
124160
  async function loadTaskStore(cwd) {
123854
124161
  try {
123855
- const raw = await readFile7(tasksPath(cwd), "utf-8");
124162
+ const raw = await readFile8(tasksPath(cwd), "utf-8");
123856
124163
  const data = JSON.parse(raw);
123857
124164
  if (!Array.isArray(data.tasks)) {
123858
124165
  return defaultStoreData();
@@ -124139,6 +124446,7 @@ class BuiltinCommands {
124139
124446
  loader.register(this.clearCommand());
124140
124447
  loader.register(this.newCommand());
124141
124448
  loader.register(this.sessionCommand());
124449
+ loader.register(this.resumeCommand());
124142
124450
  loader.register(this.statusCommand());
124143
124451
  loader.register(this.tokensCommand());
124144
124452
  loader.register(this.contextCommand());
@@ -124166,6 +124474,7 @@ class BuiltinCommands {
124166
124474
  loader.register(this.hooksCommand());
124167
124475
  loader.register(this.feedbackCommand());
124168
124476
  loader.register(this.schedulesCommand());
124477
+ loader.register(this.heartbeatCommand());
124169
124478
  loader.register(this.connectorsCommand());
124170
124479
  loader.register(this.securityLogCommand());
124171
124480
  loader.register(this.guardrailsCommand());
@@ -124174,6 +124483,7 @@ class BuiltinCommands {
124174
124483
  loader.register(this.secretsCommand());
124175
124484
  loader.register(this.jobsCommand());
124176
124485
  loader.register(this.messagesCommand());
124486
+ loader.register(this.webhooksCommand());
124177
124487
  loader.register(this.tasksCommand());
124178
124488
  loader.register(this.exitCommand());
124179
124489
  }
@@ -124908,6 +125218,10 @@ Usage: /identity create --template <name>
124908
125218
  context.emit("done");
124909
125219
  return { handled: true };
124910
125220
  }
125221
+ if (action === "edit") {
125222
+ context.emit("done");
125223
+ return { handled: true, showPanel: "identity", panelInitialValue: `edit:${match.id}` };
125224
+ }
124911
125225
  context.emit("text", `
124912
125226
  ## Identity Details
124913
125227
 
@@ -124987,6 +125301,8 @@ To update fields, use the web UI or edit the identity file directly.
124987
125301
  context.emit("text", `/identity switch <name|id> Switch to identity
124988
125302
  `);
124989
125303
  context.emit("text", `/identity show <name|id> Show identity details
125304
+ `);
125305
+ context.emit("text", `/identity edit <name|id> Edit identity in panel
124990
125306
  `);
124991
125307
  context.emit("text", `/identity set-default <name|id> Set as default
124992
125308
  `);
@@ -126978,6 +127294,197 @@ To enable:
126978
127294
  context.emit("text", `Unknown messages command: ${subcommand}
126979
127295
  `);
126980
127296
  context.emit("text", `Use /messages help for available commands.
127297
+ `);
127298
+ context.emit("done");
127299
+ return { handled: true };
127300
+ }
127301
+ };
127302
+ }
127303
+ webhooksCommand() {
127304
+ return {
127305
+ name: "webhooks",
127306
+ description: "Manage webhooks for receiving push events from external sources",
127307
+ builtin: true,
127308
+ selfHandled: true,
127309
+ content: "",
127310
+ handler: async (args, context) => {
127311
+ const trimmed = args.trim();
127312
+ const [subcommand, ...rest] = trimmed.split(/\s+/);
127313
+ const subArgs = rest.join(" ");
127314
+ if (!subcommand || subcommand === "ui") {
127315
+ context.emit("done");
127316
+ return { handled: true, showPanel: "webhooks" };
127317
+ }
127318
+ const manager = context.getWebhooksManager?.();
127319
+ if (!manager) {
127320
+ context.emit("text", `Webhooks are not enabled. Set webhooks.enabled: true in config.
127321
+ `);
127322
+ context.emit("done");
127323
+ return { handled: true };
127324
+ }
127325
+ if (subcommand === "list") {
127326
+ try {
127327
+ const webhooks = await manager.list();
127328
+ if (webhooks.length === 0) {
127329
+ context.emit("text", `No webhooks registered. Use /webhooks create <name> <source> to create one.
127330
+ `);
127331
+ } else {
127332
+ context.emit("text", `Webhooks (${webhooks.length}):
127333
+
127334
+ `);
127335
+ for (const wh of webhooks) {
127336
+ const statusIcon = wh.status === "active" ? "\u25CF" : wh.status === "paused" ? "\u25D0" : "\u2717";
127337
+ const lastDelivery = wh.lastDeliveryAt ? new Date(wh.lastDeliveryAt).toLocaleDateString() : "never";
127338
+ context.emit("text", ` ${statusIcon} ${wh.name} (${wh.id})
127339
+ `);
127340
+ context.emit("text", ` Source: ${wh.source} | Events: ${wh.deliveryCount} | Last: ${lastDelivery}
127341
+ `);
127342
+ }
127343
+ }
127344
+ } catch (error) {
127345
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127346
+ `);
127347
+ }
127348
+ context.emit("done");
127349
+ return { handled: true };
127350
+ }
127351
+ if (subcommand === "create") {
127352
+ const parts = subArgs.split(/\s+/);
127353
+ const name = parts[0];
127354
+ const source = parts[1] || "custom";
127355
+ if (!name) {
127356
+ context.emit("text", `Usage: /webhooks create <name> [source]
127357
+ `);
127358
+ context.emit("text", `Example: /webhooks create gmail-hook gmail
127359
+ `);
127360
+ context.emit("done");
127361
+ return { handled: true };
127362
+ }
127363
+ try {
127364
+ const result = await manager.create({ name, source });
127365
+ if (result.success) {
127366
+ context.emit("text", `Webhook created!
127367
+
127368
+ `);
127369
+ context.emit("text", ` ID: ${result.webhookId}
127370
+ `);
127371
+ context.emit("text", ` URL: ${result.url}
127372
+ `);
127373
+ context.emit("text", ` Secret: ${result.secret}
127374
+ `);
127375
+ context.emit("text", `
127376
+ Configure the external source with the URL and secret above.
127377
+ `);
127378
+ } else {
127379
+ context.emit("text", `Error: ${result.message}
127380
+ `);
127381
+ }
127382
+ } catch (error) {
127383
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127384
+ `);
127385
+ }
127386
+ context.emit("done");
127387
+ return { handled: true };
127388
+ }
127389
+ if (subcommand === "delete") {
127390
+ const id = subArgs.trim();
127391
+ if (!id) {
127392
+ context.emit("text", `Usage: /webhooks delete <webhook-id>
127393
+ `);
127394
+ context.emit("done");
127395
+ return { handled: true };
127396
+ }
127397
+ try {
127398
+ const result = await manager.delete(id);
127399
+ context.emit("text", `${result.message}
127400
+ `);
127401
+ } catch (error) {
127402
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127403
+ `);
127404
+ }
127405
+ context.emit("done");
127406
+ return { handled: true };
127407
+ }
127408
+ if (subcommand === "events") {
127409
+ const webhookId = subArgs.trim();
127410
+ if (!webhookId) {
127411
+ context.emit("text", `Usage: /webhooks events <webhook-id>
127412
+ `);
127413
+ context.emit("done");
127414
+ return { handled: true };
127415
+ }
127416
+ try {
127417
+ const events = await manager.listEvents(webhookId, { limit: 20 });
127418
+ if (events.length === 0) {
127419
+ context.emit("text", `No events received for this webhook.
127420
+ `);
127421
+ } else {
127422
+ context.emit("text", `Recent events (${events.length}):
127423
+
127424
+ `);
127425
+ for (const evt of events) {
127426
+ const statusIcon = evt.status === "pending" ? "\u23F3" : evt.status === "injected" ? "\uD83D\uDCE8" : "\u2713";
127427
+ context.emit("text", ` ${statusIcon} ${evt.eventType} (${evt.id})
127428
+ `);
127429
+ context.emit("text", ` ${new Date(evt.timestamp).toLocaleString()} | ${evt.preview}
127430
+ `);
127431
+ }
127432
+ }
127433
+ } catch (error) {
127434
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127435
+ `);
127436
+ }
127437
+ context.emit("done");
127438
+ return { handled: true };
127439
+ }
127440
+ if (subcommand === "test") {
127441
+ const id = subArgs.trim();
127442
+ if (!id) {
127443
+ context.emit("text", `Usage: /webhooks test <webhook-id>
127444
+ `);
127445
+ context.emit("done");
127446
+ return { handled: true };
127447
+ }
127448
+ try {
127449
+ const result = await manager.sendTestEvent(id);
127450
+ if (result.success) {
127451
+ context.emit("text", `Test event sent! Event ID: ${result.eventId}
127452
+ `);
127453
+ } else {
127454
+ context.emit("text", `Error: ${result.message}
127455
+ `);
127456
+ }
127457
+ } catch (error) {
127458
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127459
+ `);
127460
+ }
127461
+ context.emit("done");
127462
+ return { handled: true };
127463
+ }
127464
+ if (subcommand === "help") {
127465
+ context.emit("text", `Webhook Commands:
127466
+
127467
+ `);
127468
+ context.emit("text", `/webhooks Open webhooks panel
127469
+ `);
127470
+ context.emit("text", `/webhooks list List all webhooks
127471
+ `);
127472
+ context.emit("text", `/webhooks create <name> <source> Create a webhook
127473
+ `);
127474
+ context.emit("text", `/webhooks delete <id> Delete a webhook
127475
+ `);
127476
+ context.emit("text", `/webhooks events <id> List events for a webhook
127477
+ `);
127478
+ context.emit("text", `/webhooks test <id> Send a test event
127479
+ `);
127480
+ context.emit("text", `/webhooks help Show this help
127481
+ `);
127482
+ context.emit("done");
127483
+ return { handled: true };
127484
+ }
127485
+ context.emit("text", `Unknown command: ${subcommand}
127486
+ `);
127487
+ context.emit("text", `Use /webhooks help for available commands.
126981
127488
  `);
126982
127489
  context.emit("done");
126983
127490
  return { handled: true };
@@ -127576,6 +128083,91 @@ Usage: /session assign <agent-name>
127576
128083
  }
127577
128084
  };
127578
128085
  }
128086
+ resumeCommand() {
128087
+ return {
128088
+ name: "resume",
128089
+ description: "Resume saved sessions from disk",
128090
+ builtin: true,
128091
+ selfHandled: true,
128092
+ content: "",
128093
+ handler: async (args, context) => {
128094
+ const trimmed = args.trim().toLowerCase();
128095
+ const showAll = trimmed.includes("--all");
128096
+ const cleanedArgs = trimmed.replace("--all", "").trim();
128097
+ if (!cleanedArgs || cleanedArgs === "ui") {
128098
+ context.emit("done");
128099
+ return {
128100
+ handled: true,
128101
+ showPanel: "resume",
128102
+ panelInitialValue: showAll ? "all" : "cwd"
128103
+ };
128104
+ }
128105
+ if (cleanedArgs === "list" || cleanedArgs === "--list") {
128106
+ const allSessions = SessionStorage.listAllSessions();
128107
+ const normalizeCwd = (value) => value.replace(/\/+$/, "");
128108
+ const targetCwd = normalizeCwd(context.cwd);
128109
+ const sessions = showAll ? allSessions : allSessions.filter((session) => normalizeCwd(session.cwd) === targetCwd);
128110
+ if (sessions.length === 0) {
128111
+ context.emit("text", showAll ? `No saved sessions found.
128112
+ ` : `No saved sessions found for this directory.
128113
+ `);
128114
+ context.emit("done");
128115
+ return { handled: true };
128116
+ }
128117
+ const assistantManager = context.getAssistantManager?.();
128118
+ const assistantNames = assistantManager ? new Map(assistantManager.listAssistants().map((a) => [a.id, a.name])) : null;
128119
+ const truncate = (value, maxLen) => value.length > maxLen ? `${value.slice(0, maxLen - 3)}...` : value;
128120
+ const escapeCell = (value) => value.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
128121
+ let output = `
128122
+ `;
128123
+ if (showAll) {
128124
+ output += `| ID | Assistant | Updated | Messages | CWD |
128125
+ `;
128126
+ output += `|----|-----------|---------|----------|-----|
128127
+ `;
128128
+ } else {
128129
+ output += `| ID | Updated | Messages | CWD |
128130
+ `;
128131
+ output += `|----|---------|----------|-----|
128132
+ `;
128133
+ }
128134
+ for (const session of sessions) {
128135
+ const updated = formatRelativeTime(new Date(session.updatedAt).getTime());
128136
+ const messageCount = session.messageCount ?? 0;
128137
+ const cwd = escapeCell(truncate(singleLine2(session.cwd || ""), 48));
128138
+ const id = escapeCell(session.id.slice(0, 8));
128139
+ if (showAll) {
128140
+ const assistantLabel = session.assistantId ? assistantNames?.get(session.assistantId) || session.assistantId : "default";
128141
+ output += `| ${id} | ${escapeCell(truncate(assistantLabel, 16))} | ${updated} | ${messageCount} | ${cwd} |
128142
+ `;
128143
+ } else {
128144
+ output += `| ${id} | ${updated} | ${messageCount} | ${cwd} |
128145
+ `;
128146
+ }
128147
+ }
128148
+ context.emit("text", output);
128149
+ context.emit("done");
128150
+ return { handled: true };
128151
+ }
128152
+ context.emit("text", `
128153
+ **Resume** - Load saved sessions from disk
128154
+
128155
+ `);
128156
+ context.emit("text", `Usage:
128157
+ `);
128158
+ context.emit("text", ` /resume Open interactive panel (current folder)
128159
+ `);
128160
+ context.emit("text", ` /resume --all Open interactive panel (all sessions)
128161
+ `);
128162
+ context.emit("text", ` /resume list Show text table (current folder)
128163
+ `);
128164
+ context.emit("text", ` /resume list --all Show text table (all sessions)
128165
+ `);
128166
+ context.emit("done");
128167
+ return { handled: true };
128168
+ }
128169
+ };
128170
+ }
127579
128171
  tokensCommand() {
127580
128172
  return {
127581
128173
  name: "tokens",
@@ -128869,9 +129461,9 @@ Format the summary as a brief bullet-point list. This summary will replace the c
128869
129461
  }
128870
129462
  if (action === "show" || action === "paths") {
128871
129463
  const configPaths = [
128872
- join25(context.cwd, ".assistants", "config.json"),
128873
- join25(context.cwd, ".assistants", "config.local.json"),
128874
- join25(getConfigDir(), "config.json")
129464
+ join26(context.cwd, ".assistants", "config.json"),
129465
+ join26(context.cwd, ".assistants", "config.local.json"),
129466
+ join26(getConfigDir(), "config.json")
128875
129467
  ];
128876
129468
  let message = `
128877
129469
  **Configuration**
@@ -128880,7 +129472,7 @@ Format the summary as a brief bullet-point list. This summary will replace the c
128880
129472
  message += `**Config File Locations:**
128881
129473
  `;
128882
129474
  for (const path of configPaths) {
128883
- const exists = existsSync17(path);
129475
+ const exists = existsSync18(path);
128884
129476
  message += ` ${exists ? "\u2713" : "\u25CB"} ${path}
128885
129477
  `;
128886
129478
  }
@@ -128889,9 +129481,9 @@ Format the summary as a brief bullet-point list. This summary will replace the c
128889
129481
  message += `
128890
129482
  **Commands Directories:**
128891
129483
  `;
128892
- message += ` - Project: ${join25(context.cwd, ".assistants", "commands")}
129484
+ message += ` - Project: ${join26(context.cwd, ".assistants", "commands")}
128893
129485
  `;
128894
- message += ` - Global: ${join25(homeDir, ".assistants", "commands")}
129486
+ message += ` - Global: ${join26(homeDir, ".assistants", "commands")}
128895
129487
  `;
128896
129488
  context.emit("text", message);
128897
129489
  context.emit("done");
@@ -129724,8 +130316,8 @@ ${error instanceof Error ? error.message : String(error)}
129724
130316
  selfHandled: true,
129725
130317
  content: "",
129726
130318
  handler: async (args, context) => {
129727
- const commandsDir = join25(context.cwd, ".assistants", "commands");
129728
- mkdirSync11(commandsDir, { recursive: true });
130319
+ const commandsDir = join26(context.cwd, ".assistants", "commands");
130320
+ mkdirSync12(commandsDir, { recursive: true });
129729
130321
  const exampleCommand = `---
129730
130322
  name: reflect
129731
130323
  description: Reflect on the conversation and suggest next steps
@@ -129740,8 +130332,8 @@ Please summarize the last interaction and suggest 2-3 next steps.
129740
130332
  - Focus on clarity
129741
130333
  - Ask a follow-up question if needed
129742
130334
  `;
129743
- const examplePath = join25(commandsDir, "reflect.md");
129744
- if (!existsSync17(examplePath)) {
130335
+ const examplePath = join26(commandsDir, "reflect.md");
130336
+ if (!existsSync18(examplePath)) {
129745
130337
  writeFileSync12(examplePath, exampleCommand);
129746
130338
  }
129747
130339
  let message = `
@@ -130412,7 +131004,7 @@ Memory Statistics
130412
131004
  return { handled: true };
130413
131005
  }
130414
131006
  if (action === "export") {
130415
- const filePath = rest[0] || join25(getConfigDir(), "memories-export.json");
131007
+ const filePath = rest[0] || join26(getConfigDir(), "memories-export.json");
130416
131008
  const memories = await manager.export();
130417
131009
  try {
130418
131010
  const content = JSON.stringify(memories, null, 2);
@@ -130609,6 +131201,136 @@ Importing ${validMemories.length} valid entries (skipping ${errors.length} inval
130609
131201
  context.emit("text", ` /schedules list Show text table (this session)
130610
131202
  `);
130611
131203
  context.emit("text", ` /schedules list --all Show all schedules
131204
+ `);
131205
+ context.emit("done");
131206
+ return { handled: true };
131207
+ }
131208
+ };
131209
+ }
131210
+ heartbeatCommand() {
131211
+ return {
131212
+ name: "heartbeat",
131213
+ description: "View heartbeat status and recent heartbeat runs",
131214
+ builtin: true,
131215
+ selfHandled: true,
131216
+ content: "",
131217
+ handler: async (args, context) => {
131218
+ const trimmed = args.trim().toLowerCase();
131219
+ const showAll = trimmed.includes("--all");
131220
+ const cleanedArgs = trimmed.replace("--all", "").trim();
131221
+ const heartbeatState = context.getHeartbeatState?.() ?? null;
131222
+ const heartbeatConfig = context.getHeartbeatConfig?.() ?? null;
131223
+ const historyPathTemplate = heartbeatConfig?.historyPath;
131224
+ if (!cleanedArgs || cleanedArgs === "ui") {
131225
+ context.emit("done");
131226
+ return { handled: true, showPanel: "heartbeat" };
131227
+ }
131228
+ if (cleanedArgs === "list" || cleanedArgs === "--list") {
131229
+ const canEnumerate = !historyPathTemplate || historyPathTemplate.includes("{sessionId}");
131230
+ const sessionIds = showAll && canEnumerate ? listHeartbeatHistorySessions() : [context.sessionId];
131231
+ const rows = [];
131232
+ for (const sessionId of sessionIds) {
131233
+ const historyPath = resolveHeartbeatHistoryPath(sessionId, historyPathTemplate);
131234
+ const runs = await readHeartbeatHistory(historyPath, { order: "desc" });
131235
+ for (const run of runs) {
131236
+ rows.push({ sessionId, run });
131237
+ }
131238
+ }
131239
+ if (rows.length === 0) {
131240
+ context.emit("text", showAll ? `No heartbeat runs found.
131241
+ ` : `No heartbeat runs found for this session.
131242
+ `);
131243
+ context.emit("done");
131244
+ return { handled: true };
131245
+ }
131246
+ rows.sort((a, b) => {
131247
+ const aTime = new Date(a.run.timestamp).getTime();
131248
+ const bTime = new Date(b.run.timestamp).getTime();
131249
+ return bTime - aTime;
131250
+ });
131251
+ const rel = (iso) => formatRelativeTime(iso ? new Date(iso).getTime() : undefined);
131252
+ if (heartbeatState) {
131253
+ const stateLine = `State: ${heartbeatState.state} | Stale: ${heartbeatState.isStale ? "yes" : "no"} | Last Activity: ${rel(heartbeatState.lastActivity)}`;
131254
+ context.emit("text", `
131255
+ **Heartbeat Status**
131256
+ ${stateLine}
131257
+
131258
+ `);
131259
+ }
131260
+ const escapeCell = (value) => value.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
131261
+ let output = `
131262
+ `;
131263
+ if (showAll) {
131264
+ output += `| Session | Time | State | Last Activity | Msgs | Tools | Errors |
131265
+ `;
131266
+ output += `|--------|------|-------|---------------|------|-------|--------|
131267
+ `;
131268
+ } else {
131269
+ output += `| Time | State | Last Activity | Msgs | Tools | Errors |
131270
+ `;
131271
+ output += `|------|-------|---------------|------|-------|--------|
131272
+ `;
131273
+ }
131274
+ for (const { sessionId, run } of rows) {
131275
+ const time = rel(run.timestamp);
131276
+ const activity = rel(run.lastActivity);
131277
+ const stats = run.stats || { messagesProcessed: 0, toolCallsExecuted: 0, errorsEncountered: 0 };
131278
+ if (showAll) {
131279
+ output += `| ${escapeCell(sessionId.slice(0, 8))} | ${time} | ${run.state} | ${activity} | ${stats.messagesProcessed} | ${stats.toolCallsExecuted} | ${stats.errorsEncountered} |
131280
+ `;
131281
+ } else {
131282
+ output += `| ${time} | ${run.state} | ${activity} | ${stats.messagesProcessed} | ${stats.toolCallsExecuted} | ${stats.errorsEncountered} |
131283
+ `;
131284
+ }
131285
+ }
131286
+ context.emit("text", output);
131287
+ if (showAll && historyPathTemplate && !historyPathTemplate.includes("{sessionId}")) {
131288
+ context.emit("text", `
131289
+ Note: custom heartbeat historyPath does not include {sessionId}; showing current session only.
131290
+ `);
131291
+ }
131292
+ context.emit("done");
131293
+ return { handled: true };
131294
+ }
131295
+ if (cleanedArgs === "status") {
131296
+ if (!heartbeatState) {
131297
+ context.emit("text", `Heartbeat status unavailable.
131298
+ `);
131299
+ context.emit("done");
131300
+ return { handled: true };
131301
+ }
131302
+ const rel = (iso) => formatRelativeTime(iso ? new Date(iso).getTime() : undefined);
131303
+ context.emit("text", `
131304
+ **Heartbeat Status**
131305
+ `);
131306
+ context.emit("text", `State: ${heartbeatState.state}
131307
+ `);
131308
+ context.emit("text", `Enabled: ${heartbeatState.enabled ? "yes" : "no"}
131309
+ `);
131310
+ context.emit("text", `Stale: ${heartbeatState.isStale ? "yes" : "no"}
131311
+ `);
131312
+ context.emit("text", `Last Activity: ${rel(heartbeatState.lastActivity)}
131313
+ `);
131314
+ context.emit("text", `Uptime: ${heartbeatState.uptimeSeconds}s
131315
+ `);
131316
+ context.emit("done");
131317
+ return { handled: true };
131318
+ }
131319
+ context.emit("text", `
131320
+ **Heartbeat** - View heartbeat status and run history
131321
+
131322
+ `);
131323
+ context.emit("text", `Usage:
131324
+ `);
131325
+ context.emit("text", ` /heartbeat Open interactive panel
131326
+ `);
131327
+ context.emit("text", ` /heartbeat ui Open interactive panel
131328
+ `);
131329
+ context.emit("text", ` /heartbeat list Show text table (this session)
131330
+ `);
131331
+ context.emit("text", ` /heartbeat list --all Show text table for all sessions
131332
+ `);
131333
+ context.emit("text", ` /heartbeat status Show current heartbeat status
130612
131334
  `);
130613
131335
  context.emit("done");
130614
131336
  return { handled: true };
@@ -131094,10 +131816,9 @@ async function createLLMClient(config) {
131094
131816
  await init_config();
131095
131817
 
131096
131818
  // ../core/src/heartbeat/manager.ts
131097
- import { dirname as dirname12 } from "path";
131098
- import { mkdirSync as mkdirSync12 } from "fs";
131099
- import { readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
131100
-
131819
+ import { dirname as dirname13 } from "path";
131820
+ import { mkdirSync as mkdirSync13 } from "fs";
131821
+ import { readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
131101
131822
  class HeartbeatManager {
131102
131823
  config;
131103
131824
  state = "idle";
@@ -131106,6 +131827,7 @@ class HeartbeatManager {
131106
131827
  stats;
131107
131828
  intervalId;
131108
131829
  listeners = new Set;
131830
+ lastHeartbeatAt = null;
131109
131831
  constructor(config) {
131110
131832
  this.config = config;
131111
131833
  this.startTime = Date.now();
@@ -131116,8 +131838,8 @@ class HeartbeatManager {
131116
131838
  errorsEncountered: 0,
131117
131839
  uptimeSeconds: 0
131118
131840
  };
131119
- const dir = dirname12(config.persistPath);
131120
- mkdirSync12(dir, { recursive: true });
131841
+ const dir = dirname13(config.persistPath);
131842
+ mkdirSync13(dir, { recursive: true });
131121
131843
  }
131122
131844
  start(sessionId) {
131123
131845
  if (this.intervalId)
@@ -131159,6 +131881,10 @@ class HeartbeatManager {
131159
131881
  getStartTime() {
131160
131882
  return this.startTime;
131161
131883
  }
131884
+ getNextHeartbeatAt() {
131885
+ const base = this.lastHeartbeatAt ?? Date.now();
131886
+ return base + this.config.intervalMs;
131887
+ }
131162
131888
  getStats() {
131163
131889
  return {
131164
131890
  ...this.stats,
@@ -131173,14 +131899,17 @@ class HeartbeatManager {
131173
131899
  this.lastActivity = Date.now();
131174
131900
  }
131175
131901
  async emit(sessionId) {
131902
+ const now2 = Date.now();
131903
+ this.lastHeartbeatAt = now2;
131904
+ const uptimeSeconds = Math.floor((now2 - this.startTime) / 1000);
131176
131905
  const heartbeat = {
131177
131906
  sessionId,
131178
- timestamp: new Date().toISOString(),
131907
+ timestamp: new Date(now2).toISOString(),
131179
131908
  state: this.state,
131180
131909
  lastActivity: new Date(this.lastActivity).toISOString(),
131181
131910
  stats: {
131182
131911
  ...this.stats,
131183
- uptimeSeconds: Math.floor((Date.now() - this.startTime) / 1000)
131912
+ uptimeSeconds
131184
131913
  }
131185
131914
  };
131186
131915
  for (const listener of this.listeners) {
@@ -131191,11 +131920,14 @@ class HeartbeatManager {
131191
131920
  async persist(heartbeat) {
131192
131921
  try {
131193
131922
  await writeFile7(this.config.persistPath, JSON.stringify(heartbeat, null, 2));
131923
+ if (this.config.historyPath) {
131924
+ await appendHeartbeatHistory(this.config.historyPath, heartbeat);
131925
+ }
131194
131926
  } catch {}
131195
131927
  }
131196
131928
  static async checkStale(path2, thresholdMs) {
131197
131929
  try {
131198
- const content = await readFile8(path2, "utf-8");
131930
+ const content = await readFile9(path2, "utf-8");
131199
131931
  const heartbeat = JSON.parse(content);
131200
131932
  const age = Date.now() - new Date(heartbeat.timestamp).getTime();
131201
131933
  return { isStale: age > thresholdMs, lastHeartbeat: heartbeat };
@@ -131205,15 +131937,15 @@ class HeartbeatManager {
131205
131937
  }
131206
131938
  }
131207
131939
  // ../core/src/heartbeat/persistence.ts
131208
- import { dirname as dirname13 } from "path";
131209
- import { mkdirSync as mkdirSync13 } from "fs";
131210
- import { readFile as readFile9, writeFile as writeFile8, unlink as unlink5 } from "fs/promises";
131940
+ import { dirname as dirname14 } from "path";
131941
+ import { mkdirSync as mkdirSync14 } from "fs";
131942
+ import { readFile as readFile10, writeFile as writeFile8, unlink as unlink5 } from "fs/promises";
131211
131943
 
131212
131944
  class StatePersistence {
131213
131945
  path;
131214
131946
  constructor(path2) {
131215
131947
  this.path = path2;
131216
- mkdirSync13(dirname13(path2), { recursive: true });
131948
+ mkdirSync14(dirname14(path2), { recursive: true });
131217
131949
  }
131218
131950
  async save(state) {
131219
131951
  try {
@@ -131222,7 +131954,7 @@ class StatePersistence {
131222
131954
  }
131223
131955
  async load() {
131224
131956
  try {
131225
- const content = await readFile9(this.path, "utf-8");
131957
+ const content = await readFile10(this.path, "utf-8");
131226
131958
  return JSON.parse(content);
131227
131959
  } catch {
131228
131960
  return null;
@@ -131354,7 +132086,7 @@ async function ensureWatchdogSchedule(cwd, sessionId, intervalMs = DEFAULT_WATCH
131354
132086
  }
131355
132087
  // ../core/src/heartbeat/install-skills.ts
131356
132088
  await init_config();
131357
- import { join as join28 } from "path";
132089
+ import { join as join29 } from "path";
131358
132090
  var MAIN_LOOP_SKILL = `---
131359
132091
  name: main-loop
131360
132092
  description: Autonomous heartbeat \u2014 review goals, check async results, act on pending items, and schedule next wakeup.
@@ -131418,8 +132150,8 @@ You are the watchdog. Your only job is to verify the heartbeat is running and fo
131418
132150
  `;
131419
132151
  async function writeSkillIfMissing(dir, skillName, content) {
131420
132152
  const { mkdir: mkdir10, writeFile: writeFile9, access } = await import("fs/promises");
131421
- const skillDir = join28(dir, `skill-${skillName}`);
131422
- const skillFile = join28(skillDir, "SKILL.md");
132153
+ const skillDir = join29(dir, `skill-${skillName}`);
132154
+ const skillFile = join29(skillDir, "SKILL.md");
131423
132155
  try {
131424
132156
  await access(skillFile);
131425
132157
  return false;
@@ -131429,7 +132161,7 @@ async function writeSkillIfMissing(dir, skillName, content) {
131429
132161
  return true;
131430
132162
  }
131431
132163
  async function installHeartbeatSkills() {
131432
- const sharedSkillsDir = join28(getConfigDir(), "shared", "skills");
132164
+ const sharedSkillsDir = join29(getConfigDir(), "shared", "skills");
131433
132165
  const installed = [];
131434
132166
  const results = await Promise.all([
131435
132167
  writeSkillIfMissing(sharedSkillsDir, "main-loop", MAIN_LOOP_SKILL),
@@ -131603,15 +132335,15 @@ class EnergyManager {
131603
132335
  }
131604
132336
  }
131605
132337
  // ../core/src/energy/storage.ts
131606
- import { dirname as dirname14 } from "path";
131607
- import { mkdirSync as mkdirSync14 } from "fs";
131608
- import { readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
132338
+ import { dirname as dirname15 } from "path";
132339
+ import { mkdirSync as mkdirSync15 } from "fs";
132340
+ import { readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
131609
132341
 
131610
132342
  class EnergyStorage {
131611
132343
  path;
131612
132344
  constructor(path2) {
131613
132345
  this.path = path2;
131614
- mkdirSync14(dirname14(path2), { recursive: true });
132346
+ mkdirSync15(dirname15(path2), { recursive: true });
131615
132347
  }
131616
132348
  async save(state) {
131617
132349
  try {
@@ -131620,7 +132352,7 @@ class EnergyStorage {
131620
132352
  }
131621
132353
  async load() {
131622
132354
  try {
131623
- const content = await readFile10(this.path, "utf-8");
132355
+ const content = await readFile11(this.path, "utf-8");
131624
132356
  return JSON.parse(content);
131625
132357
  } catch {
131626
132358
  return null;
@@ -131678,15 +132410,15 @@ function validateToolCalls(toolCalls, tools) {
131678
132410
  }
131679
132411
 
131680
132412
  // ../core/src/voice/utils.ts
131681
- import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
132413
+ import { existsSync as existsSync21, readFileSync as readFileSync13 } from "fs";
131682
132414
  import { homedir as homedir17 } from "os";
131683
- import { join as join29 } from "path";
132415
+ import { join as join30 } from "path";
131684
132416
  import { spawnSync } from "child_process";
131685
132417
  function loadApiKeyFromSecrets3(key) {
131686
132418
  const envHome = process.env.HOME || process.env.USERPROFILE;
131687
132419
  const homeDir = envHome && envHome.trim().length > 0 ? envHome : homedir17();
131688
- const secretsPath = join29(homeDir, ".secrets");
131689
- if (!existsSync20(secretsPath))
132420
+ const secretsPath = join30(homeDir, ".secrets");
132421
+ if (!existsSync21(secretsPath))
131690
132422
  return;
131691
132423
  try {
131692
132424
  const content = readFileSync13(secretsPath, "utf-8");
@@ -131798,7 +132530,7 @@ class SystemSTT {
131798
132530
  // ../core/src/voice/tts.ts
131799
132531
  import { spawnSync as spawnSync2 } from "child_process";
131800
132532
  import { tmpdir as tmpdir2 } from "os";
131801
- import { join as join30 } from "path";
132533
+ import { join as join31 } from "path";
131802
132534
  import { readFileSync as readFileSync14, unlinkSync as unlinkSync4 } from "fs";
131803
132535
  class ElevenLabsTTS {
131804
132536
  apiKey;
@@ -131902,7 +132634,7 @@ class SystemTTS {
131902
132634
  if (!say) {
131903
132635
  throw new Error('System TTS not available: missing "say" command.');
131904
132636
  }
131905
- const output = join30(tmpdir2(), `assistants-tts-${Date.now()}.aiff`);
132637
+ const output = join31(tmpdir2(), `assistants-tts-${Date.now()}.aiff`);
131906
132638
  const args = [];
131907
132639
  if (this.voiceId) {
131908
132640
  args.push("-v", this.voiceId);
@@ -131924,7 +132656,7 @@ class SystemTTS {
131924
132656
  }
131925
132657
  const espeak = findExecutable("espeak") || findExecutable("espeak-ng");
131926
132658
  if (espeak) {
131927
- const output = join30(tmpdir2(), `assistants-tts-${Date.now()}.wav`);
132659
+ const output = join31(tmpdir2(), `assistants-tts-${Date.now()}.wav`);
131928
132660
  const args = ["-w", output];
131929
132661
  if (this.voiceId) {
131930
132662
  args.push("-v", this.voiceId);
@@ -131951,14 +132683,14 @@ class SystemTTS {
131951
132683
  // ../core/src/voice/player.ts
131952
132684
  import { spawn } from "child_process";
131953
132685
  import { tmpdir as tmpdir3 } from "os";
131954
- import { join as join31 } from "path";
132686
+ import { join as join32 } from "path";
131955
132687
  import { unlink as unlink6, writeFileSync as writeFileSync13 } from "fs";
131956
132688
  class AudioPlayer {
131957
132689
  currentProcess = null;
131958
132690
  playing = false;
131959
132691
  async play(audio, options = {}) {
131960
132692
  const format = options.format ?? "mp3";
131961
- const tempFile = join31(tmpdir3(), `assistants-audio-${Date.now()}.${format}`);
132693
+ const tempFile = join32(tmpdir3(), `assistants-audio-${Date.now()}.${format}`);
131962
132694
  writeFileSync13(tempFile, Buffer.from(audio));
131963
132695
  const player = this.resolvePlayer(format);
131964
132696
  if (!player) {
@@ -132026,7 +132758,7 @@ class AudioPlayer {
132026
132758
  // ../core/src/voice/recorder.ts
132027
132759
  import { spawn as spawn2 } from "child_process";
132028
132760
  import { tmpdir as tmpdir4 } from "os";
132029
- import { join as join32 } from "path";
132761
+ import { join as join33 } from "path";
132030
132762
  import { readFileSync as readFileSync15, unlink as unlink7 } from "fs";
132031
132763
  class AudioRecorder {
132032
132764
  currentProcess = null;
@@ -132039,7 +132771,7 @@ class AudioRecorder {
132039
132771
  const duration = options.durationSeconds ?? 5;
132040
132772
  const sampleRate = options.sampleRate ?? 16000;
132041
132773
  const channels = options.channels ?? 1;
132042
- const output = join32(tmpdir4(), `assistants-record-${Date.now()}.wav`);
132774
+ const output = join33(tmpdir4(), `assistants-record-${Date.now()}.wav`);
132043
132775
  this.currentOutputPath = output;
132044
132776
  this.stoppedIntentionally = false;
132045
132777
  const recorder = this.resolveRecorder(sampleRate, channels, duration, output);
@@ -132240,15 +132972,15 @@ class VoiceManager {
132240
132972
  }
132241
132973
  // ../core/src/identity/assistant-manager.ts
132242
132974
  init_src2();
132243
- import { existsSync as existsSync22 } from "fs";
132244
- import { mkdir as mkdir11, readFile as readFile12, writeFile as writeFile11, rm as rm3 } from "fs/promises";
132245
- import { join as join34 } from "path";
132975
+ import { existsSync as existsSync23 } from "fs";
132976
+ import { mkdir as mkdir11, readFile as readFile13, writeFile as writeFile11, rm as rm3 } from "fs/promises";
132977
+ import { join as join35 } from "path";
132246
132978
 
132247
132979
  // ../core/src/identity/identity-manager.ts
132248
132980
  init_src2();
132249
- import { existsSync as existsSync21 } from "fs";
132250
- import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile10, rm as rm2 } from "fs/promises";
132251
- import { join as join33 } from "path";
132981
+ import { existsSync as existsSync22 } from "fs";
132982
+ import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile10, rm as rm2 } from "fs/promises";
132983
+ import { join as join34 } from "path";
132252
132984
  var SAFE_ID_PATTERN6 = /^[a-zA-Z0-9_-]+$/;
132253
132985
  function isValidId2(id) {
132254
132986
  return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN6.test(id);
@@ -132289,20 +133021,20 @@ class IdentityManager {
132289
133021
  this.basePath = basePath;
132290
133022
  }
132291
133023
  get identitiesRoot() {
132292
- return join33(this.basePath, "assistants", this.assistantId, "identities");
133024
+ return join34(this.basePath, "assistants", this.assistantId, "identities");
132293
133025
  }
132294
133026
  get indexPath() {
132295
- return join33(this.identitiesRoot, "index.json");
133027
+ return join34(this.identitiesRoot, "index.json");
132296
133028
  }
132297
133029
  get activePath() {
132298
- return join33(this.identitiesRoot, "active.json");
133030
+ return join34(this.identitiesRoot, "active.json");
132299
133031
  }
132300
133032
  identityPath(id) {
132301
133033
  validateId(id, "identityId");
132302
- return join33(this.identitiesRoot, `${id}.json`);
133034
+ return join34(this.identitiesRoot, `${id}.json`);
132303
133035
  }
132304
133036
  assistantConfigPath() {
132305
- return join33(this.basePath, "assistants", this.assistantId, "config.json");
133037
+ return join34(this.basePath, "assistants", this.assistantId, "config.json");
132306
133038
  }
132307
133039
  async initialize() {
132308
133040
  await mkdir10(this.identitiesRoot, { recursive: true });
@@ -132428,11 +133160,11 @@ class IdentityManager {
132428
133160
  `);
132429
133161
  }
132430
133162
  async readIndex() {
132431
- if (!existsSync21(this.indexPath)) {
133163
+ if (!existsSync22(this.indexPath)) {
132432
133164
  return { identities: [] };
132433
133165
  }
132434
133166
  try {
132435
- const raw = await readFile11(this.indexPath, "utf-8");
133167
+ const raw = await readFile12(this.indexPath, "utf-8");
132436
133168
  const data = JSON.parse(raw);
132437
133169
  const identities = Array.isArray(data.identities) ? data.identities : [];
132438
133170
  return { identities: identities.filter(isValidId2) };
@@ -132454,10 +133186,10 @@ class IdentityManager {
132454
133186
  }
132455
133187
  async readIdentity(id) {
132456
133188
  const path2 = this.identityPath(id);
132457
- if (!existsSync21(path2))
133189
+ if (!existsSync22(path2))
132458
133190
  return null;
132459
133191
  try {
132460
- const raw = await readFile11(path2, "utf-8");
133192
+ const raw = await readFile12(path2, "utf-8");
132461
133193
  return JSON.parse(raw);
132462
133194
  } catch {
132463
133195
  return null;
@@ -132468,10 +133200,10 @@ class IdentityManager {
132468
133200
  await writeFile10(this.identityPath(identity.id), JSON.stringify(identity, null, 2));
132469
133201
  }
132470
133202
  async readActive() {
132471
- if (!existsSync21(this.activePath))
133203
+ if (!existsSync22(this.activePath))
132472
133204
  return null;
132473
133205
  try {
132474
- const raw = await readFile11(this.activePath, "utf-8");
133206
+ const raw = await readFile12(this.activePath, "utf-8");
132475
133207
  const data = JSON.parse(raw);
132476
133208
  const id = data.id || null;
132477
133209
  if (id && !isValidId2(id)) {
@@ -132487,10 +133219,10 @@ class IdentityManager {
132487
133219
  await writeFile10(this.activePath, JSON.stringify({ id }, null, 2));
132488
133220
  }
132489
133221
  async loadAssistant() {
132490
- if (!existsSync21(this.assistantConfigPath()))
133222
+ if (!existsSync22(this.assistantConfigPath()))
132491
133223
  return null;
132492
133224
  try {
132493
- const raw = await readFile11(this.assistantConfigPath(), "utf-8");
133225
+ const raw = await readFile12(this.assistantConfigPath(), "utf-8");
132494
133226
  return JSON.parse(raw);
132495
133227
  } catch {
132496
133228
  return null;
@@ -132520,17 +133252,17 @@ class AssistantManager {
132520
133252
  this.basePath = basePath;
132521
133253
  }
132522
133254
  get assistantsRoot() {
132523
- return join34(this.basePath, "assistants");
133255
+ return join35(this.basePath, "assistants");
132524
133256
  }
132525
133257
  get indexPath() {
132526
- return join34(this.assistantsRoot, "index.json");
133258
+ return join35(this.assistantsRoot, "index.json");
132527
133259
  }
132528
133260
  get activePath() {
132529
- return join34(this.basePath, "active.json");
133261
+ return join35(this.basePath, "active.json");
132530
133262
  }
132531
133263
  assistantConfigPath(id) {
132532
133264
  validateId2(id, "assistantId");
132533
- return join34(this.assistantsRoot, id, "config.json");
133265
+ return join35(this.assistantsRoot, id, "config.json");
132534
133266
  }
132535
133267
  async initialize() {
132536
133268
  await mkdir11(this.assistantsRoot, { recursive: true });
@@ -132585,7 +133317,7 @@ class AssistantManager {
132585
133317
  if (!this.assistants.has(id)) {
132586
133318
  throw new Error(`Assistant ${id} not found`);
132587
133319
  }
132588
- await rm3(join34(this.assistantsRoot, id), { recursive: true, force: true });
133320
+ await rm3(join35(this.assistantsRoot, id), { recursive: true, force: true });
132589
133321
  this.assistants.delete(id);
132590
133322
  await this.removeFromIndex(id);
132591
133323
  if (this.activeId === id) {
@@ -132616,11 +133348,11 @@ class AssistantManager {
132616
133348
  return new IdentityManager(assistantId, this.basePath);
132617
133349
  }
132618
133350
  async readIndex() {
132619
- if (!existsSync22(this.indexPath)) {
133351
+ if (!existsSync23(this.indexPath)) {
132620
133352
  return { assistants: [] };
132621
133353
  }
132622
133354
  try {
132623
- const raw = await readFile12(this.indexPath, "utf-8");
133355
+ const raw = await readFile13(this.indexPath, "utf-8");
132624
133356
  const data = JSON.parse(raw);
132625
133357
  const assistants = Array.isArray(data.assistants) ? data.assistants : [];
132626
133358
  return { assistants: assistants.filter(isValidId3) };
@@ -132642,10 +133374,10 @@ class AssistantManager {
132642
133374
  }
132643
133375
  async readAssistant(id) {
132644
133376
  const configPath = this.assistantConfigPath(id);
132645
- if (!existsSync22(configPath))
133377
+ if (!existsSync23(configPath))
132646
133378
  return null;
132647
133379
  try {
132648
- const raw = await readFile12(configPath, "utf-8");
133380
+ const raw = await readFile13(configPath, "utf-8");
132649
133381
  return JSON.parse(raw);
132650
133382
  } catch {
132651
133383
  return null;
@@ -132653,15 +133385,15 @@ class AssistantManager {
132653
133385
  }
132654
133386
  async persistAssistant(assistant) {
132655
133387
  validateId2(assistant.id, "assistantId");
132656
- const dir = join34(this.assistantsRoot, assistant.id);
133388
+ const dir = join35(this.assistantsRoot, assistant.id);
132657
133389
  await mkdir11(dir, { recursive: true });
132658
133390
  await writeFile11(this.assistantConfigPath(assistant.id), JSON.stringify(assistant, null, 2));
132659
133391
  }
132660
133392
  async readActive() {
132661
- if (!existsSync22(this.activePath))
133393
+ if (!existsSync23(this.activePath))
132662
133394
  return null;
132663
133395
  try {
132664
- const raw = await readFile12(this.activePath, "utf-8");
133396
+ const raw = await readFile13(this.activePath, "utf-8");
132665
133397
  const data = JSON.parse(raw);
132666
133398
  const id = data.id || null;
132667
133399
  if (id && !isValidId3(id)) {
@@ -132678,7 +133410,7 @@ class AssistantManager {
132678
133410
  }
132679
133411
  }
132680
133412
  // ../core/src/inbox/inbox-manager.ts
132681
- import { join as join37 } from "path";
133413
+ import { join as join38 } from "path";
132682
133414
 
132683
133415
  // ../../node_modules/.bun/@aws-sdk+middleware-expect-continue@3.972.3/node_modules/@aws-sdk/middleware-expect-continue/dist-es/index.js
132684
133416
  var import_protocol_http = __toESM(require_dist_cjs2(), 1);
@@ -141536,8 +142268,8 @@ class S3InboxClient {
141536
142268
  }
141537
142269
 
141538
142270
  // ../core/src/inbox/storage/local-cache.ts
141539
- import { join as join36, basename as basename5 } from "path";
141540
- import { mkdir as mkdir12, readFile as readFile14, writeFile as writeFile13, rm as rm4, readdir as readdir4, stat as stat3 } from "fs/promises";
142271
+ import { join as join37, basename as basename5 } from "path";
142272
+ import { mkdir as mkdir12, readFile as readFile15, writeFile as writeFile13, rm as rm4, readdir as readdir4, stat as stat3 } from "fs/promises";
141541
142273
  import { createHash as createHash5 } from "crypto";
141542
142274
  var STRICT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
141543
142275
  var SAFE_FILENAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
@@ -141574,18 +142306,18 @@ class LocalInboxCache {
141574
142306
  validateAssistantId(options.assistantId);
141575
142307
  this.assistantId = options.assistantId;
141576
142308
  this.basePath = options.basePath;
141577
- this.cacheDir = join36(this.basePath, this.assistantId);
142309
+ this.cacheDir = join37(this.basePath, this.assistantId);
141578
142310
  }
141579
142311
  async ensureDirectories() {
141580
- await mkdir12(join36(this.cacheDir, "emails"), { recursive: true });
141581
- await mkdir12(join36(this.cacheDir, "attachments"), { recursive: true });
142312
+ await mkdir12(join37(this.cacheDir, "emails"), { recursive: true });
142313
+ await mkdir12(join37(this.cacheDir, "attachments"), { recursive: true });
141582
142314
  }
141583
142315
  async loadIndex() {
141584
142316
  if (this.index)
141585
142317
  return this.index;
141586
142318
  try {
141587
- const indexPath = join36(this.cacheDir, "index.json");
141588
- const content = await readFile14(indexPath, "utf-8");
142319
+ const indexPath = join37(this.cacheDir, "index.json");
142320
+ const content = await readFile15(indexPath, "utf-8");
141589
142321
  this.index = JSON.parse(content);
141590
142322
  return this.index;
141591
142323
  } catch {
@@ -141597,7 +142329,7 @@ class LocalInboxCache {
141597
142329
  if (!this.index)
141598
142330
  return;
141599
142331
  await this.ensureDirectories();
141600
- const indexPath = join36(this.cacheDir, "index.json");
142332
+ const indexPath = join37(this.cacheDir, "index.json");
141601
142333
  await writeFile13(indexPath, JSON.stringify(this.index, null, 2));
141602
142334
  }
141603
142335
  async saveEmail(email) {
@@ -141606,7 +142338,7 @@ class LocalInboxCache {
141606
142338
  throw new Error(`Failed to create safe filename for email ID: "${email.id}"`);
141607
142339
  }
141608
142340
  await this.ensureDirectories();
141609
- const emailPath = join36(this.cacheDir, "emails", `${filename}.json`);
142341
+ const emailPath = join37(this.cacheDir, "emails", `${filename}.json`);
141610
142342
  await writeFile13(emailPath, JSON.stringify(email, null, 2));
141611
142343
  const index = await this.loadIndex();
141612
142344
  const existingIdx = index.emails.findIndex((e3) => e3.id === email.id);
@@ -141641,8 +142373,8 @@ class LocalInboxCache {
141641
142373
  return null;
141642
142374
  }
141643
142375
  try {
141644
- const emailPath = join36(this.cacheDir, "emails", `${filename}.json`);
141645
- const content = await readFile14(emailPath, "utf-8");
142376
+ const emailPath = join37(this.cacheDir, "emails", `${filename}.json`);
142377
+ const content = await readFile15(emailPath, "utf-8");
141646
142378
  return JSON.parse(content);
141647
142379
  } catch {
141648
142380
  return null;
@@ -141701,9 +142433,9 @@ class LocalInboxCache {
141701
142433
  if (!safeFilename) {
141702
142434
  throw new Error("Invalid attachment filename");
141703
142435
  }
141704
- const attachmentDir = join36(this.cacheDir, "attachments", emailFilename);
142436
+ const attachmentDir = join37(this.cacheDir, "attachments", emailFilename);
141705
142437
  await mkdir12(attachmentDir, { recursive: true });
141706
- const attachmentPath = join36(attachmentDir, safeFilename);
142438
+ const attachmentPath = join37(attachmentDir, safeFilename);
141707
142439
  await writeFile13(attachmentPath, content);
141708
142440
  return attachmentPath;
141709
142441
  }
@@ -141717,7 +142449,7 @@ class LocalInboxCache {
141717
142449
  return null;
141718
142450
  }
141719
142451
  try {
141720
- const attachmentPath = join36(this.cacheDir, "attachments", emailFilename, safeFilename);
142452
+ const attachmentPath = join37(this.cacheDir, "attachments", emailFilename, safeFilename);
141721
142453
  await stat3(attachmentPath);
141722
142454
  return attachmentPath;
141723
142455
  } catch {
@@ -141749,10 +142481,10 @@ class LocalInboxCache {
141749
142481
  for (const { id, filename } of toRemove) {
141750
142482
  index.emails = index.emails.filter((e3) => e3.id !== id);
141751
142483
  try {
141752
- await rm4(join36(this.cacheDir, "emails", `${filename}.json`));
142484
+ await rm4(join37(this.cacheDir, "emails", `${filename}.json`));
141753
142485
  } catch {}
141754
142486
  try {
141755
- await rm4(join36(this.cacheDir, "attachments", filename), { recursive: true });
142487
+ await rm4(join37(this.cacheDir, "attachments", filename), { recursive: true });
141756
142488
  } catch {}
141757
142489
  }
141758
142490
  if (toRemove.length > 0) {
@@ -141763,20 +142495,20 @@ class LocalInboxCache {
141763
142495
  async getCacheSize() {
141764
142496
  let totalSize = 0;
141765
142497
  try {
141766
- const emailsDir = join36(this.cacheDir, "emails");
142498
+ const emailsDir = join37(this.cacheDir, "emails");
141767
142499
  const files = await readdir4(emailsDir);
141768
142500
  for (const file of files) {
141769
- const fileStat = await stat3(join36(emailsDir, file));
142501
+ const fileStat = await stat3(join37(emailsDir, file));
141770
142502
  totalSize += fileStat.size;
141771
142503
  }
141772
142504
  } catch {}
141773
142505
  try {
141774
- const attachmentsDir = join36(this.cacheDir, "attachments");
142506
+ const attachmentsDir = join37(this.cacheDir, "attachments");
141775
142507
  const dirs = await readdir4(attachmentsDir);
141776
142508
  for (const dir of dirs) {
141777
- const files = await readdir4(join36(attachmentsDir, dir));
142509
+ const files = await readdir4(join37(attachmentsDir, dir));
141778
142510
  for (const file of files) {
141779
- const fileStat = await stat3(join36(attachmentsDir, dir, file));
142511
+ const fileStat = await stat3(join37(attachmentsDir, dir, file));
141780
142512
  totalSize += fileStat.size;
141781
142513
  }
141782
142514
  }
@@ -144364,7 +145096,7 @@ class InboxManager {
144364
145096
  }
144365
145097
  }
144366
145098
  function createInboxManager(assistantId, assistantName, config, configDir) {
144367
- const basePath = join37(configDir, "inbox");
145099
+ const basePath = join38(configDir, "inbox");
144368
145100
  return new InboxManager({
144369
145101
  assistantId,
144370
145102
  assistantName,
@@ -147125,13 +147857,13 @@ init_src2();
147125
147857
 
147126
147858
  // ../core/src/messages/storage/local-storage.ts
147127
147859
  await init_runtime();
147128
- import { join as join38 } from "path";
147860
+ import { join as join39 } from "path";
147129
147861
  import { homedir as homedir19 } from "os";
147130
147862
  import { mkdir as mkdir13, readdir as readdir5, rm as rm5 } from "fs/promises";
147131
147863
  function getMessagesBasePath() {
147132
147864
  const envOverride = process.env.ASSISTANTS_DIR;
147133
147865
  const home = envOverride && envOverride.trim() ? envOverride : homedir19();
147134
- return join38(home, ".assistants", "messages");
147866
+ return join39(home, ".assistants", "messages");
147135
147867
  }
147136
147868
  var SAFE_ID_PATTERN8 = /^[a-zA-Z0-9_-]+$/;
147137
147869
 
@@ -147152,27 +147884,27 @@ class LocalMessagesStorage {
147152
147884
  const assistantPath = this.getAssistantPath(assistantId);
147153
147885
  await Promise.all([
147154
147886
  mkdir13(this.basePath, { recursive: true }),
147155
- mkdir13(join38(assistantPath, "messages"), { recursive: true }),
147156
- mkdir13(join38(assistantPath, "threads"), { recursive: true })
147887
+ mkdir13(join39(assistantPath, "messages"), { recursive: true }),
147888
+ mkdir13(join39(assistantPath, "threads"), { recursive: true })
147157
147889
  ]);
147158
147890
  }
147159
147891
  getAssistantPath(assistantId) {
147160
147892
  this.validateSafeId(assistantId, "assistantId");
147161
- return join38(this.basePath, assistantId);
147893
+ return join39(this.basePath, assistantId);
147162
147894
  }
147163
147895
  getIndexPath(assistantId) {
147164
- return join38(this.getAssistantPath(assistantId), "index.json");
147896
+ return join39(this.getAssistantPath(assistantId), "index.json");
147165
147897
  }
147166
147898
  getMessagePath(assistantId, messageId) {
147167
147899
  this.validateSafeId(messageId, "messageId");
147168
- return join38(this.getAssistantPath(assistantId), "messages", `${messageId}.json`);
147900
+ return join39(this.getAssistantPath(assistantId), "messages", `${messageId}.json`);
147169
147901
  }
147170
147902
  getThreadPath(assistantId, threadId) {
147171
147903
  this.validateSafeId(threadId, "threadId");
147172
- return join38(this.getAssistantPath(assistantId), "threads", `${threadId}.json`);
147904
+ return join39(this.getAssistantPath(assistantId), "threads", `${threadId}.json`);
147173
147905
  }
147174
147906
  getRegistryPath() {
147175
- return join38(this.basePath, "registry.json");
147907
+ return join39(this.basePath, "registry.json");
147176
147908
  }
147177
147909
  async loadRegistry() {
147178
147910
  try {
@@ -147456,7 +148188,7 @@ class LocalMessagesStorage {
147456
148188
  async listThreads(assistantId) {
147457
148189
  const runtime = getRuntime();
147458
148190
  const assistantPath = this.getAssistantPath(assistantId);
147459
- const threadsDir = join38(assistantPath, "threads");
148191
+ const threadsDir = join39(assistantPath, "threads");
147460
148192
  try {
147461
148193
  const files = await readdir5(threadsDir);
147462
148194
  const threads = [];
@@ -147464,7 +148196,7 @@ class LocalMessagesStorage {
147464
148196
  if (!file.endsWith(".json"))
147465
148197
  continue;
147466
148198
  try {
147467
- const threadFile = runtime.file(join38(threadsDir, file));
148199
+ const threadFile = runtime.file(join39(threadsDir, file));
147468
148200
  const thread = await threadFile.json();
147469
148201
  threads.push(thread);
147470
148202
  } catch {}
@@ -147520,8 +148252,8 @@ class LocalMessagesStorage {
147520
148252
  }
147521
148253
 
147522
148254
  // ../core/src/messages/watcher.ts
147523
- import { watch, existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
147524
- import { join as join39 } from "path";
148255
+ import { watch, existsSync as existsSync24, readdirSync as readdirSync7 } from "fs";
148256
+ import { join as join40 } from "path";
147525
148257
  class InboxWatcher {
147526
148258
  assistantId;
147527
148259
  inboxPath;
@@ -147532,14 +148264,14 @@ class InboxWatcher {
147532
148264
  constructor(assistantId, basePath) {
147533
148265
  this.assistantId = assistantId;
147534
148266
  const base = basePath || getMessagesBasePath();
147535
- this.inboxPath = join39(base, assistantId, "messages");
148267
+ this.inboxPath = join40(base, assistantId, "messages");
147536
148268
  }
147537
148269
  start() {
147538
148270
  if (this.running)
147539
148271
  return;
147540
148272
  this.running = true;
147541
148273
  this.snapshotExisting();
147542
- if (!existsSync23(this.inboxPath)) {
148274
+ if (!existsSync24(this.inboxPath)) {
147543
148275
  this.pollForDirectory();
147544
148276
  return;
147545
148277
  }
@@ -147564,8 +148296,8 @@ class InboxWatcher {
147564
148296
  }
147565
148297
  snapshotExisting() {
147566
148298
  try {
147567
- if (existsSync23(this.inboxPath)) {
147568
- const files = readdirSync6(this.inboxPath);
148299
+ if (existsSync24(this.inboxPath)) {
148300
+ const files = readdirSync7(this.inboxPath);
147569
148301
  for (const file of files) {
147570
148302
  if (file.endsWith(".json")) {
147571
148303
  this.knownFiles.add(file);
@@ -147607,7 +148339,7 @@ class InboxWatcher {
147607
148339
  clearInterval(interval);
147608
148340
  return;
147609
148341
  }
147610
- if (existsSync23(this.inboxPath)) {
148342
+ if (existsSync24(this.inboxPath)) {
147611
148343
  clearInterval(interval);
147612
148344
  this.snapshotExisting();
147613
148345
  this.startWatching();
@@ -148284,25 +149016,1096 @@ function registerMessagesTools(registry2, getMessagesManager) {
148284
149016
  registry2.register(tool, executors[tool.name]);
148285
149017
  }
148286
149018
  }
148287
- // ../core/src/sessions/store.ts
148288
- import { join as join40 } from "path";
149019
+ // ../core/src/webhooks/storage/local-storage.ts
149020
+ await init_runtime();
149021
+ import { join as join41 } from "path";
148289
149022
  import { homedir as homedir20 } from "os";
148290
- import { existsSync as existsSync24, mkdirSync as mkdirSync15, writeFileSync as writeFileSync14, readFileSync as readFileSync17, readdirSync as readdirSync7, unlinkSync as unlinkSync5 } from "fs";
149023
+ import { mkdir as mkdir14, readdir as readdir6, rm as rm6 } from "fs/promises";
149024
+ function getWebhooksBasePath() {
149025
+ const envOverride = process.env.ASSISTANTS_DIR;
149026
+ const home = envOverride && envOverride.trim() ? envOverride : homedir20();
149027
+ return join41(home, ".assistants", "webhooks");
149028
+ }
149029
+ var SAFE_ID_PATTERN9 = /^[a-zA-Z0-9_-]+$/;
149030
+
149031
+ class LocalWebhookStorage {
149032
+ basePath;
149033
+ constructor(options = {}) {
149034
+ this.basePath = options.basePath || getWebhooksBasePath();
149035
+ }
149036
+ validateSafeId(id, idType) {
149037
+ if (!id || typeof id !== "string") {
149038
+ throw new Error(`Invalid ${idType}: must be a non-empty string`);
149039
+ }
149040
+ if (!SAFE_ID_PATTERN9.test(id)) {
149041
+ throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
149042
+ }
149043
+ }
149044
+ async ensureDirectories(webhookId) {
149045
+ const dirs = [
149046
+ mkdir14(this.basePath, { recursive: true }),
149047
+ mkdir14(join41(this.basePath, "registrations"), { recursive: true }),
149048
+ mkdir14(join41(this.basePath, "events"), { recursive: true }),
149049
+ mkdir14(join41(this.basePath, "deliveries"), { recursive: true })
149050
+ ];
149051
+ if (webhookId) {
149052
+ this.validateSafeId(webhookId, "webhookId");
149053
+ dirs.push(mkdir14(join41(this.basePath, "events", webhookId), { recursive: true }));
149054
+ dirs.push(mkdir14(join41(this.basePath, "deliveries", webhookId), { recursive: true }));
149055
+ }
149056
+ await Promise.all(dirs);
149057
+ }
149058
+ getIndexPath() {
149059
+ return join41(this.basePath, "index.json");
149060
+ }
149061
+ getRegistrationPath(webhookId) {
149062
+ this.validateSafeId(webhookId, "webhookId");
149063
+ return join41(this.basePath, "registrations", `${webhookId}.json`);
149064
+ }
149065
+ getEventIndexPath(webhookId) {
149066
+ this.validateSafeId(webhookId, "webhookId");
149067
+ return join41(this.basePath, "events", webhookId, "index.json");
149068
+ }
149069
+ getEventPath(webhookId, eventId) {
149070
+ this.validateSafeId(webhookId, "webhookId");
149071
+ this.validateSafeId(eventId, "eventId");
149072
+ return join41(this.basePath, "events", webhookId, `${eventId}.json`);
149073
+ }
149074
+ getDeliveryPath(webhookId, deliveryId) {
149075
+ this.validateSafeId(webhookId, "webhookId");
149076
+ this.validateSafeId(deliveryId, "deliveryId");
149077
+ return join41(this.basePath, "deliveries", webhookId, `${deliveryId}.json`);
149078
+ }
149079
+ async loadIndex() {
149080
+ try {
149081
+ const runtime = getRuntime();
149082
+ const file = runtime.file(this.getIndexPath());
149083
+ if (!await file.exists()) {
149084
+ return { webhooks: [], lastUpdated: new Date().toISOString() };
149085
+ }
149086
+ return await file.json();
149087
+ } catch {
149088
+ return { webhooks: [], lastUpdated: new Date().toISOString() };
149089
+ }
149090
+ }
149091
+ async saveIndex(index) {
149092
+ const runtime = getRuntime();
149093
+ await mkdir14(this.basePath, { recursive: true });
149094
+ index.lastUpdated = new Date().toISOString();
149095
+ await runtime.write(this.getIndexPath(), JSON.stringify(index, null, 2));
149096
+ }
149097
+ async saveRegistration(registration) {
149098
+ const runtime = getRuntime();
149099
+ await this.ensureDirectories(registration.id);
149100
+ await runtime.write(this.getRegistrationPath(registration.id), JSON.stringify(registration, null, 2));
149101
+ const index = await this.loadIndex();
149102
+ const existing = index.webhooks.findIndex((w6) => w6.id === registration.id);
149103
+ const listItem = {
149104
+ id: registration.id,
149105
+ name: registration.name,
149106
+ source: registration.source,
149107
+ status: registration.status,
149108
+ deliveryCount: registration.deliveryCount,
149109
+ createdAt: registration.createdAt,
149110
+ lastDeliveryAt: registration.lastDeliveryAt
149111
+ };
149112
+ if (existing >= 0) {
149113
+ index.webhooks[existing] = listItem;
149114
+ } else {
149115
+ index.webhooks.unshift(listItem);
149116
+ }
149117
+ await this.saveIndex(index);
149118
+ }
149119
+ async loadRegistration(webhookId) {
149120
+ try {
149121
+ const runtime = getRuntime();
149122
+ const file = runtime.file(this.getRegistrationPath(webhookId));
149123
+ if (!await file.exists()) {
149124
+ return null;
149125
+ }
149126
+ return await file.json();
149127
+ } catch {
149128
+ return null;
149129
+ }
149130
+ }
149131
+ async deleteRegistration(webhookId) {
149132
+ try {
149133
+ const regPath = this.getRegistrationPath(webhookId);
149134
+ const runtime = getRuntime();
149135
+ const file = runtime.file(regPath);
149136
+ if (!await file.exists()) {
149137
+ return false;
149138
+ }
149139
+ await rm6(regPath);
149140
+ const index = await this.loadIndex();
149141
+ const idx = index.webhooks.findIndex((w6) => w6.id === webhookId);
149142
+ if (idx >= 0) {
149143
+ index.webhooks.splice(idx, 1);
149144
+ await this.saveIndex(index);
149145
+ }
149146
+ try {
149147
+ await rm6(join41(this.basePath, "events", webhookId), { recursive: true, force: true });
149148
+ await rm6(join41(this.basePath, "deliveries", webhookId), { recursive: true, force: true });
149149
+ } catch {}
149150
+ return true;
149151
+ } catch {
149152
+ return false;
149153
+ }
149154
+ }
149155
+ async listRegistrations() {
149156
+ const index = await this.loadIndex();
149157
+ return index.webhooks;
149158
+ }
149159
+ async loadEventIndex(webhookId) {
149160
+ try {
149161
+ const runtime = getRuntime();
149162
+ const file = runtime.file(this.getEventIndexPath(webhookId));
149163
+ if (!await file.exists()) {
149164
+ return {
149165
+ events: [],
149166
+ lastUpdated: new Date().toISOString(),
149167
+ totalEvents: 0,
149168
+ pendingCount: 0
149169
+ };
149170
+ }
149171
+ return await file.json();
149172
+ } catch {
149173
+ return {
149174
+ events: [],
149175
+ lastUpdated: new Date().toISOString(),
149176
+ totalEvents: 0,
149177
+ pendingCount: 0
149178
+ };
149179
+ }
149180
+ }
149181
+ async saveEventIndex(webhookId, index) {
149182
+ const runtime = getRuntime();
149183
+ this.validateSafeId(webhookId, "webhookId");
149184
+ await mkdir14(join41(this.basePath, "events", webhookId), { recursive: true });
149185
+ index.lastUpdated = new Date().toISOString();
149186
+ await runtime.write(this.getEventIndexPath(webhookId), JSON.stringify(index, null, 2));
149187
+ }
149188
+ async saveEvent(event) {
149189
+ const runtime = getRuntime();
149190
+ await this.ensureDirectories(event.webhookId);
149191
+ await runtime.write(this.getEventPath(event.webhookId, event.id), JSON.stringify(event, null, 2));
149192
+ const eventIndex = await this.loadEventIndex(event.webhookId);
149193
+ const payloadStr = JSON.stringify(event.payload);
149194
+ const listItem = {
149195
+ id: event.id,
149196
+ source: event.source,
149197
+ eventType: event.eventType,
149198
+ preview: payloadStr.slice(0, 100) + (payloadStr.length > 100 ? "..." : ""),
149199
+ timestamp: event.timestamp,
149200
+ status: event.status
149201
+ };
149202
+ eventIndex.events.unshift(listItem);
149203
+ eventIndex.totalEvents++;
149204
+ if (event.status === "pending") {
149205
+ eventIndex.pendingCount++;
149206
+ }
149207
+ await this.saveEventIndex(event.webhookId, eventIndex);
149208
+ }
149209
+ async loadEvent(webhookId, eventId) {
149210
+ try {
149211
+ const runtime = getRuntime();
149212
+ const file = runtime.file(this.getEventPath(webhookId, eventId));
149213
+ if (!await file.exists()) {
149214
+ return null;
149215
+ }
149216
+ return await file.json();
149217
+ } catch {
149218
+ return null;
149219
+ }
149220
+ }
149221
+ async updateEventStatus(webhookId, eventId, status, timestamp) {
149222
+ const runtime = getRuntime();
149223
+ const event = await this.loadEvent(webhookId, eventId);
149224
+ if (!event)
149225
+ return;
149226
+ const oldStatus = event.status;
149227
+ event.status = status;
149228
+ if (status === "injected" && timestamp) {
149229
+ event.injectedAt = timestamp;
149230
+ }
149231
+ await runtime.write(this.getEventPath(webhookId, eventId), JSON.stringify(event, null, 2));
149232
+ const eventIndex = await this.loadEventIndex(webhookId);
149233
+ const indexItem = eventIndex.events.find((e6) => e6.id === eventId);
149234
+ if (indexItem) {
149235
+ indexItem.status = status;
149236
+ }
149237
+ if (oldStatus === "pending" && status !== "pending") {
149238
+ eventIndex.pendingCount = Math.max(0, eventIndex.pendingCount - 1);
149239
+ }
149240
+ await this.saveEventIndex(webhookId, eventIndex);
149241
+ }
149242
+ async listEvents(webhookId, options) {
149243
+ const eventIndex = await this.loadEventIndex(webhookId);
149244
+ let events = [...eventIndex.events];
149245
+ if (options?.pendingOnly) {
149246
+ events = events.filter((e6) => e6.status === "pending");
149247
+ }
149248
+ if (options?.limit && options.limit > 0) {
149249
+ events = events.slice(0, options.limit);
149250
+ }
149251
+ return events;
149252
+ }
149253
+ async saveDelivery(delivery) {
149254
+ const runtime = getRuntime();
149255
+ this.validateSafeId(delivery.webhookId, "webhookId");
149256
+ await mkdir14(join41(this.basePath, "deliveries", delivery.webhookId), { recursive: true });
149257
+ await runtime.write(this.getDeliveryPath(delivery.webhookId, delivery.id), JSON.stringify(delivery, null, 2));
149258
+ }
149259
+ async listDeliveries(webhookId, options) {
149260
+ this.validateSafeId(webhookId, "webhookId");
149261
+ const deliveriesDir = join41(this.basePath, "deliveries", webhookId);
149262
+ try {
149263
+ const files = await readdir6(deliveriesDir);
149264
+ const runtime = getRuntime();
149265
+ const deliveries = [];
149266
+ for (const file of files) {
149267
+ if (!file.endsWith(".json"))
149268
+ continue;
149269
+ try {
149270
+ const deliveryFile = runtime.file(join41(deliveriesDir, file));
149271
+ const delivery = await deliveryFile.json();
149272
+ deliveries.push(delivery);
149273
+ } catch {}
149274
+ }
149275
+ deliveries.sort((a5, b8) => new Date(b8.receivedAt).getTime() - new Date(a5.receivedAt).getTime());
149276
+ if (options?.limit && options.limit > 0) {
149277
+ return deliveries.slice(0, options.limit);
149278
+ }
149279
+ return deliveries;
149280
+ } catch {
149281
+ return [];
149282
+ }
149283
+ }
149284
+ async cleanupEvents(webhookId, maxAgeDays) {
149285
+ const cutoffDate = new Date;
149286
+ cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
149287
+ const cutoffTime = cutoffDate.getTime();
149288
+ const eventIndex = await this.loadEventIndex(webhookId);
149289
+ const toDelete = [];
149290
+ for (const evt of eventIndex.events) {
149291
+ const evtTime = new Date(evt.timestamp).getTime();
149292
+ if (evtTime < cutoffTime) {
149293
+ toDelete.push(evt.id);
149294
+ }
149295
+ }
149296
+ for (const id of toDelete) {
149297
+ try {
149298
+ await rm6(this.getEventPath(webhookId, id));
149299
+ } catch {}
149300
+ }
149301
+ eventIndex.events = eventIndex.events.filter((e6) => !toDelete.includes(e6.id));
149302
+ eventIndex.pendingCount = eventIndex.events.filter((e6) => e6.status === "pending").length;
149303
+ await this.saveEventIndex(webhookId, eventIndex);
149304
+ return toDelete.length;
149305
+ }
149306
+ async enforceMaxEvents(webhookId, maxEvents) {
149307
+ const eventIndex = await this.loadEventIndex(webhookId);
149308
+ if (eventIndex.events.length <= maxEvents) {
149309
+ return 0;
149310
+ }
149311
+ const sorted = [...eventIndex.events].sort((a5, b8) => new Date(a5.timestamp).getTime() - new Date(b8.timestamp).getTime());
149312
+ const toDelete = sorted.slice(0, eventIndex.events.length - maxEvents);
149313
+ for (const evt of toDelete) {
149314
+ try {
149315
+ await rm6(this.getEventPath(webhookId, evt.id));
149316
+ } catch {}
149317
+ }
149318
+ const deleteIds = new Set(toDelete.map((e6) => e6.id));
149319
+ eventIndex.events = eventIndex.events.filter((e6) => !deleteIds.has(e6.id));
149320
+ eventIndex.pendingCount = eventIndex.events.filter((e6) => e6.status === "pending").length;
149321
+ await this.saveEventIndex(webhookId, eventIndex);
149322
+ return toDelete.length;
149323
+ }
149324
+ }
149325
+
149326
+ // ../core/src/webhooks/watcher.ts
149327
+ import { watch as watch2, existsSync as existsSync25, readdirSync as readdirSync8 } from "fs";
149328
+ import { join as join42 } from "path";
149329
+ class WebhookEventWatcher {
149330
+ basePath;
149331
+ eventsPath;
149332
+ watchers = new Map;
149333
+ callbacks = new Set;
149334
+ knownFiles = new Map;
149335
+ running = false;
149336
+ directoryWatcher = null;
149337
+ constructor(basePath) {
149338
+ this.basePath = basePath || getWebhooksBasePath();
149339
+ this.eventsPath = join42(this.basePath, "events");
149340
+ }
149341
+ start() {
149342
+ if (this.running)
149343
+ return;
149344
+ this.running = true;
149345
+ if (!existsSync25(this.eventsPath)) {
149346
+ this.pollForDirectory();
149347
+ return;
149348
+ }
149349
+ this.startWatchingAll();
149350
+ }
149351
+ stop() {
149352
+ this.running = false;
149353
+ for (const [, watcher] of this.watchers) {
149354
+ watcher.close();
149355
+ }
149356
+ this.watchers.clear();
149357
+ if (this.directoryWatcher) {
149358
+ this.directoryWatcher.close();
149359
+ this.directoryWatcher = null;
149360
+ }
149361
+ this.callbacks.clear();
149362
+ this.knownFiles.clear();
149363
+ }
149364
+ onNewEvent(cb2) {
149365
+ this.callbacks.add(cb2);
149366
+ return () => {
149367
+ this.callbacks.delete(cb2);
149368
+ };
149369
+ }
149370
+ isRunning() {
149371
+ return this.running;
149372
+ }
149373
+ startWatchingAll() {
149374
+ try {
149375
+ this.directoryWatcher = watch2(this.eventsPath, (eventType, filename) => {
149376
+ if (!filename || eventType !== "rename")
149377
+ return;
149378
+ const dirPath = join42(this.eventsPath, filename);
149379
+ if (existsSync25(dirPath) && !this.watchers.has(filename)) {
149380
+ this.watchWebhookDir(filename);
149381
+ }
149382
+ });
149383
+ this.directoryWatcher.on("error", () => {
149384
+ if (this.running) {
149385
+ this.directoryWatcher?.close();
149386
+ this.directoryWatcher = null;
149387
+ setTimeout(() => {
149388
+ if (this.running)
149389
+ this.startWatchingAll();
149390
+ }, 5000);
149391
+ }
149392
+ });
149393
+ } catch {}
149394
+ try {
149395
+ const dirs = readdirSync8(this.eventsPath);
149396
+ for (const dir of dirs) {
149397
+ this.watchWebhookDir(dir);
149398
+ }
149399
+ } catch {}
149400
+ }
149401
+ watchWebhookDir(webhookId) {
149402
+ if (this.watchers.has(webhookId))
149403
+ return;
149404
+ const dirPath = join42(this.eventsPath, webhookId);
149405
+ if (!existsSync25(dirPath))
149406
+ return;
149407
+ const known = new Set;
149408
+ try {
149409
+ const files = readdirSync8(dirPath);
149410
+ for (const file of files) {
149411
+ if (file.endsWith(".json") && file !== "index.json") {
149412
+ known.add(file);
149413
+ }
149414
+ }
149415
+ } catch {}
149416
+ this.knownFiles.set(webhookId, known);
149417
+ try {
149418
+ const watcher = watch2(dirPath, (eventType, filename) => {
149419
+ if (!filename || !filename.endsWith(".json") || filename === "index.json")
149420
+ return;
149421
+ const knownSet = this.knownFiles.get(webhookId);
149422
+ if (eventType === "rename" && knownSet && !knownSet.has(filename)) {
149423
+ knownSet.add(filename);
149424
+ const eventId = filename.replace(".json", "");
149425
+ this.notifyCallbacks(webhookId, eventId);
149426
+ }
149427
+ });
149428
+ watcher.on("error", () => {
149429
+ if (this.running) {
149430
+ watcher.close();
149431
+ this.watchers.delete(webhookId);
149432
+ setTimeout(() => {
149433
+ if (this.running)
149434
+ this.watchWebhookDir(webhookId);
149435
+ }, 5000);
149436
+ }
149437
+ });
149438
+ this.watchers.set(webhookId, watcher);
149439
+ } catch {}
149440
+ }
149441
+ pollForDirectory() {
149442
+ if (!this.running)
149443
+ return;
149444
+ const interval = setInterval(() => {
149445
+ if (!this.running) {
149446
+ clearInterval(interval);
149447
+ return;
149448
+ }
149449
+ if (existsSync25(this.eventsPath)) {
149450
+ clearInterval(interval);
149451
+ this.startWatchingAll();
149452
+ }
149453
+ }, 5000);
149454
+ }
149455
+ notifyCallbacks(webhookId, eventId) {
149456
+ for (const cb2 of this.callbacks) {
149457
+ try {
149458
+ cb2(webhookId, eventId);
149459
+ } catch {}
149460
+ }
149461
+ }
149462
+ }
149463
+
149464
+ // ../core/src/webhooks/manager.ts
149465
+ init_crypto();
149466
+
149467
+ class WebhooksManager {
149468
+ assistantId;
149469
+ config;
149470
+ storage;
149471
+ watcher = null;
149472
+ eventCallbacks = new Set;
149473
+ rateLimits = new Map;
149474
+ constructor(options) {
149475
+ this.assistantId = options.assistantId;
149476
+ this.config = options.config;
149477
+ this.storage = new LocalWebhookStorage({
149478
+ basePath: options.config.storage?.basePath || getWebhooksBasePath()
149479
+ });
149480
+ }
149481
+ async initialize() {
149482
+ await this.storage.ensureDirectories();
149483
+ }
149484
+ async create(input) {
149485
+ try {
149486
+ const webhookId = generateWebhookId();
149487
+ const secret = generateWebhookSecret();
149488
+ const now2 = new Date().toISOString();
149489
+ const registration = {
149490
+ id: webhookId,
149491
+ name: input.name,
149492
+ source: input.source,
149493
+ description: input.description,
149494
+ secret,
149495
+ eventsFilter: input.eventsFilter || [],
149496
+ status: "active",
149497
+ deliveryCount: 0,
149498
+ createdAt: now2,
149499
+ updatedAt: now2
149500
+ };
149501
+ await this.storage.saveRegistration(registration);
149502
+ const url = `/api/v1/webhooks/receive/${webhookId}`;
149503
+ return {
149504
+ success: true,
149505
+ message: `Webhook "${input.name}" created for source "${input.source}"`,
149506
+ webhookId,
149507
+ secret,
149508
+ url
149509
+ };
149510
+ } catch (error2) {
149511
+ return {
149512
+ success: false,
149513
+ message: `Failed to create webhook: ${error2 instanceof Error ? error2.message : String(error2)}`
149514
+ };
149515
+ }
149516
+ }
149517
+ async list() {
149518
+ return this.storage.listRegistrations();
149519
+ }
149520
+ async get(webhookId) {
149521
+ return this.storage.loadRegistration(webhookId);
149522
+ }
149523
+ async update(input) {
149524
+ try {
149525
+ const registration = await this.storage.loadRegistration(input.id);
149526
+ if (!registration) {
149527
+ return { success: false, message: `Webhook "${input.id}" not found.` };
149528
+ }
149529
+ if (input.name !== undefined)
149530
+ registration.name = input.name;
149531
+ if (input.description !== undefined)
149532
+ registration.description = input.description;
149533
+ if (input.eventsFilter !== undefined)
149534
+ registration.eventsFilter = input.eventsFilter;
149535
+ if (input.status !== undefined)
149536
+ registration.status = input.status;
149537
+ registration.updatedAt = new Date().toISOString();
149538
+ await this.storage.saveRegistration(registration);
149539
+ return {
149540
+ success: true,
149541
+ message: `Webhook "${registration.name}" updated.`,
149542
+ webhookId: registration.id
149543
+ };
149544
+ } catch (error2) {
149545
+ return {
149546
+ success: false,
149547
+ message: `Failed to update webhook: ${error2 instanceof Error ? error2.message : String(error2)}`
149548
+ };
149549
+ }
149550
+ }
149551
+ async delete(webhookId) {
149552
+ const deleted = await this.storage.deleteRegistration(webhookId);
149553
+ if (deleted) {
149554
+ return { success: true, message: `Webhook ${webhookId} deleted.` };
149555
+ }
149556
+ return { success: false, message: `Webhook ${webhookId} not found.` };
149557
+ }
149558
+ async receiveEvent(input) {
149559
+ try {
149560
+ const registration = await this.storage.loadRegistration(input.webhookId);
149561
+ if (!registration) {
149562
+ return { success: false, message: "Webhook not found." };
149563
+ }
149564
+ if (registration.status !== "active") {
149565
+ return { success: false, message: "Webhook is not active." };
149566
+ }
149567
+ if (!this.checkRateLimit(input.webhookId)) {
149568
+ return { success: false, message: "Rate limit exceeded." };
149569
+ }
149570
+ const maxTimestampAge = this.config.security?.maxTimestampAgeMs || 300000;
149571
+ if (!isTimestampValid(input.timestamp, maxTimestampAge)) {
149572
+ return { success: false, message: "Timestamp too old or invalid." };
149573
+ }
149574
+ const payloadStr = JSON.stringify(input.payload);
149575
+ if (!verifySignature(payloadStr, input.signature, registration.secret)) {
149576
+ return { success: false, message: "Invalid signature." };
149577
+ }
149578
+ if (registration.eventsFilter.length > 0 && !registration.eventsFilter.includes(input.eventType)) {
149579
+ return { success: false, message: `Event type "${input.eventType}" not accepted by this webhook.` };
149580
+ }
149581
+ const eventId = generateEventId();
149582
+ const deliveryId = generateDeliveryId();
149583
+ const now2 = new Date().toISOString();
149584
+ const event = {
149585
+ id: eventId,
149586
+ webhookId: input.webhookId,
149587
+ source: registration.source,
149588
+ eventType: input.eventType,
149589
+ payload: input.payload,
149590
+ timestamp: input.timestamp,
149591
+ signature: input.signature,
149592
+ status: "pending"
149593
+ };
149594
+ await this.storage.saveEvent(event);
149595
+ const delivery = {
149596
+ id: deliveryId,
149597
+ webhookId: input.webhookId,
149598
+ eventId,
149599
+ receivedAt: now2,
149600
+ status: "accepted",
149601
+ httpStatus: 200,
149602
+ remoteIp: input.remoteIp
149603
+ };
149604
+ await this.storage.saveDelivery(delivery);
149605
+ registration.deliveryCount++;
149606
+ registration.lastDeliveryAt = now2;
149607
+ registration.updatedAt = now2;
149608
+ await this.storage.saveRegistration(registration);
149609
+ return {
149610
+ success: true,
149611
+ message: "Event received.",
149612
+ deliveryId,
149613
+ eventId
149614
+ };
149615
+ } catch (error2) {
149616
+ return {
149617
+ success: false,
149618
+ message: `Failed to process event: ${error2 instanceof Error ? error2.message : String(error2)}`
149619
+ };
149620
+ }
149621
+ }
149622
+ async sendTestEvent(webhookId) {
149623
+ const registration = await this.storage.loadRegistration(webhookId);
149624
+ if (!registration) {
149625
+ return { success: false, message: `Webhook "${webhookId}" not found.` };
149626
+ }
149627
+ const { signPayload: signPayload2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
149628
+ const payload = { test: true, message: "Test event from assistant", timestamp: new Date().toISOString() };
149629
+ const payloadStr = JSON.stringify(payload);
149630
+ const signature = signPayload2(payloadStr, registration.secret);
149631
+ return this.receiveEvent({
149632
+ webhookId,
149633
+ payload,
149634
+ signature,
149635
+ timestamp: new Date().toISOString(),
149636
+ eventType: "test"
149637
+ });
149638
+ }
149639
+ async listEvents(webhookId, options) {
149640
+ return this.storage.listEvents(webhookId, options);
149641
+ }
149642
+ async listDeliveries(webhookId, options) {
149643
+ return this.storage.listDeliveries(webhookId, options);
149644
+ }
149645
+ async getPendingForInjection() {
149646
+ const injectionConfig = this.config.injection || {};
149647
+ if (injectionConfig.enabled === false) {
149648
+ return [];
149649
+ }
149650
+ const maxPerTurn = injectionConfig.maxPerTurn || 5;
149651
+ const webhooks = await this.storage.listRegistrations();
149652
+ const allPending = [];
149653
+ for (const webhook of webhooks) {
149654
+ if (webhook.status !== "active")
149655
+ continue;
149656
+ const pendingItems = await this.storage.listEvents(webhook.id, { pendingOnly: true });
149657
+ for (const item of pendingItems) {
149658
+ const event = await this.storage.loadEvent(webhook.id, item.id);
149659
+ if (event && event.status === "pending") {
149660
+ allPending.push(event);
149661
+ }
149662
+ }
149663
+ }
149664
+ allPending.sort((a5, b8) => new Date(a5.timestamp).getTime() - new Date(b8.timestamp).getTime());
149665
+ return allPending.slice(0, maxPerTurn);
149666
+ }
149667
+ async markInjected(events) {
149668
+ const now2 = new Date().toISOString();
149669
+ for (const { webhookId, eventId } of events) {
149670
+ await this.storage.updateEventStatus(webhookId, eventId, "injected", now2);
149671
+ }
149672
+ }
149673
+ buildInjectionContext(events) {
149674
+ if (events.length === 0) {
149675
+ return "";
149676
+ }
149677
+ const lines = [];
149678
+ lines.push("## Pending Webhook Events");
149679
+ lines.push("");
149680
+ lines.push(`You have ${events.length} pending webhook event(s):`);
149681
+ for (const evt of events) {
149682
+ lines.push("");
149683
+ lines.push(`### ${evt.source}: ${evt.eventType}`);
149684
+ lines.push(`**Webhook:** ${evt.webhookId} | **Received:** ${formatDate2(evt.timestamp)}`);
149685
+ lines.push("");
149686
+ lines.push("```json");
149687
+ lines.push(JSON.stringify(evt.payload, null, 2));
149688
+ lines.push("```");
149689
+ lines.push(`*Event ID: ${evt.id}*`);
149690
+ lines.push("---");
149691
+ }
149692
+ lines.push("");
149693
+ lines.push("Process these events as appropriate. Use webhook tools to manage webhooks.");
149694
+ return lines.join(`
149695
+ `);
149696
+ }
149697
+ startWatching() {
149698
+ if (this.watcher)
149699
+ return;
149700
+ this.watcher = new WebhookEventWatcher(this.config.storage?.basePath);
149701
+ this.watcher.onNewEvent(async (webhookId, eventId) => {
149702
+ try {
149703
+ const event = await this.storage.loadEvent(webhookId, eventId);
149704
+ if (event) {
149705
+ for (const cb2 of this.eventCallbacks) {
149706
+ try {
149707
+ cb2(event);
149708
+ } catch {}
149709
+ }
149710
+ }
149711
+ } catch {}
149712
+ });
149713
+ this.watcher.start();
149714
+ }
149715
+ stopWatching() {
149716
+ if (this.watcher) {
149717
+ this.watcher.stop();
149718
+ this.watcher = null;
149719
+ }
149720
+ this.eventCallbacks.clear();
149721
+ }
149722
+ onEvent(callback) {
149723
+ this.eventCallbacks.add(callback);
149724
+ return () => {
149725
+ this.eventCallbacks.delete(callback);
149726
+ };
149727
+ }
149728
+ isWatching() {
149729
+ return this.watcher?.isRunning() ?? false;
149730
+ }
149731
+ checkRateLimit(webhookId) {
149732
+ const maxPerMinute = this.config.security?.rateLimitPerMinute || 60;
149733
+ const now2 = Date.now();
149734
+ const windowMs = 60000;
149735
+ let entry = this.rateLimits.get(webhookId);
149736
+ if (!entry || now2 - entry.windowStart >= windowMs) {
149737
+ entry = { count: 0, windowStart: now2 };
149738
+ this.rateLimits.set(webhookId, entry);
149739
+ }
149740
+ entry.count++;
149741
+ return entry.count <= maxPerMinute;
149742
+ }
149743
+ async cleanup() {
149744
+ const maxAgeDays = this.config.storage?.maxAgeDays || 30;
149745
+ const maxEvents = this.config.storage?.maxEvents || 1000;
149746
+ const webhooks = await this.storage.listRegistrations();
149747
+ let totalDeleted = 0;
149748
+ for (const webhook of webhooks) {
149749
+ totalDeleted += await this.storage.cleanupEvents(webhook.id, maxAgeDays);
149750
+ totalDeleted += await this.storage.enforceMaxEvents(webhook.id, maxEvents);
149751
+ }
149752
+ return totalDeleted;
149753
+ }
149754
+ }
149755
+ function formatDate2(isoDate) {
149756
+ const date = new Date(isoDate);
149757
+ return date.toLocaleString();
149758
+ }
149759
+ function createWebhooksManager(assistantId, config) {
149760
+ return new WebhooksManager({
149761
+ assistantId,
149762
+ config
149763
+ });
149764
+ }
149765
+
149766
+ // ../core/src/webhooks/index.ts
149767
+ init_crypto();
149768
+
149769
+ // ../core/src/webhooks/tools.ts
149770
+ var webhookCreateTool = {
149771
+ name: "webhook_create",
149772
+ description: "Create a new webhook endpoint. Returns the URL and secret needed for the external source to send events. The secret is used for HMAC-SHA256 signature verification.",
149773
+ parameters: {
149774
+ type: "object",
149775
+ properties: {
149776
+ name: {
149777
+ type: "string",
149778
+ description: 'Human-readable name for the webhook (e.g., "Gmail notifications")'
149779
+ },
149780
+ source: {
149781
+ type: "string",
149782
+ description: 'Source identifier (e.g., "gmail", "notion", "github", "custom")'
149783
+ },
149784
+ description: {
149785
+ type: "string",
149786
+ description: "Description of what this webhook handles (optional)"
149787
+ },
149788
+ eventsFilter: {
149789
+ type: "array",
149790
+ description: 'Event types to accept (optional, empty = accept all). Example: ["message.received", "issue.opened"]',
149791
+ items: { type: "string", description: "Event type name" }
149792
+ }
149793
+ },
149794
+ required: ["name", "source"]
149795
+ }
149796
+ };
149797
+ var webhookListTool = {
149798
+ name: "webhook_list",
149799
+ description: "List all registered webhooks with their status, source, and delivery counts.",
149800
+ parameters: {
149801
+ type: "object",
149802
+ properties: {},
149803
+ required: []
149804
+ }
149805
+ };
149806
+ var webhookGetTool = {
149807
+ name: "webhook_get",
149808
+ description: "Get full details of a webhook including its secret, URL, events filter, and delivery count.",
149809
+ parameters: {
149810
+ type: "object",
149811
+ properties: {
149812
+ id: {
149813
+ type: "string",
149814
+ description: "The webhook ID (e.g., whk_abc123)"
149815
+ }
149816
+ },
149817
+ required: ["id"]
149818
+ }
149819
+ };
149820
+ var webhookUpdateTool = {
149821
+ name: "webhook_update",
149822
+ description: "Update a webhook registration. Can change name, description, events filter, or status (active/paused).",
149823
+ parameters: {
149824
+ type: "object",
149825
+ properties: {
149826
+ id: {
149827
+ type: "string",
149828
+ description: "The webhook ID to update"
149829
+ },
149830
+ name: {
149831
+ type: "string",
149832
+ description: "New name (optional)"
149833
+ },
149834
+ description: {
149835
+ type: "string",
149836
+ description: "New description (optional)"
149837
+ },
149838
+ eventsFilter: {
149839
+ type: "array",
149840
+ description: "New events filter (optional)",
149841
+ items: { type: "string", description: "Event type name" }
149842
+ },
149843
+ status: {
149844
+ type: "string",
149845
+ description: "New status: active or paused (optional)",
149846
+ enum: ["active", "paused"]
149847
+ }
149848
+ },
149849
+ required: ["id"]
149850
+ }
149851
+ };
149852
+ var webhookDeleteTool = {
149853
+ name: "webhook_delete",
149854
+ description: "Delete a webhook registration and all its event history.",
149855
+ parameters: {
149856
+ type: "object",
149857
+ properties: {
149858
+ id: {
149859
+ type: "string",
149860
+ description: "The webhook ID to delete"
149861
+ }
149862
+ },
149863
+ required: ["id"]
149864
+ }
149865
+ };
149866
+ var webhookEventsTool = {
149867
+ name: "webhook_events",
149868
+ description: "List recent events received by a webhook. Shows event type, payload preview, and status.",
149869
+ parameters: {
149870
+ type: "object",
149871
+ properties: {
149872
+ webhookId: {
149873
+ type: "string",
149874
+ description: "The webhook ID to list events for"
149875
+ },
149876
+ limit: {
149877
+ type: "number",
149878
+ description: "Maximum number of events to return (default: 20)"
149879
+ },
149880
+ pendingOnly: {
149881
+ type: "boolean",
149882
+ description: "Only show pending (unprocessed) events (default: false)"
149883
+ }
149884
+ },
149885
+ required: ["webhookId"]
149886
+ }
149887
+ };
149888
+ var webhookTestTool = {
149889
+ name: "webhook_test",
149890
+ description: "Send a test event to a webhook to verify it is working correctly.",
149891
+ parameters: {
149892
+ type: "object",
149893
+ properties: {
149894
+ id: {
149895
+ type: "string",
149896
+ description: "The webhook ID to send a test event to"
149897
+ }
149898
+ },
149899
+ required: ["id"]
149900
+ }
149901
+ };
149902
+ function createWebhookToolExecutors(getWebhooksManager) {
149903
+ return {
149904
+ webhook_create: async (input) => {
149905
+ const manager = getWebhooksManager();
149906
+ if (!manager) {
149907
+ return "Error: Webhooks are not enabled or configured. Set webhooks.enabled: true in config.";
149908
+ }
149909
+ const name2 = String(input.name || "").trim();
149910
+ const source = String(input.source || "").trim();
149911
+ const description = input.description ? String(input.description).trim() : undefined;
149912
+ const eventsFilter = Array.isArray(input.eventsFilter) ? input.eventsFilter.map(String) : undefined;
149913
+ if (!name2)
149914
+ return "Error: Webhook name is required.";
149915
+ if (!source)
149916
+ return "Error: Source identifier is required.";
149917
+ const result = await manager.create({ name: name2, source, description, eventsFilter });
149918
+ if (result.success) {
149919
+ const lines = [];
149920
+ lines.push(`Webhook created successfully!`);
149921
+ lines.push("");
149922
+ lines.push(`**ID:** ${result.webhookId}`);
149923
+ lines.push(`**URL:** ${result.url}`);
149924
+ lines.push(`**Secret:** ${result.secret}`);
149925
+ lines.push("");
149926
+ lines.push("**Setup instructions for the external source:**");
149927
+ lines.push(`1. Set webhook URL to: \`<your-base-url>${result.url}\``);
149928
+ lines.push(`2. Set signing secret to: \`${result.secret}\``);
149929
+ lines.push("3. Include these headers with each POST:");
149930
+ lines.push(" - `X-Webhook-Signature`: HMAC-SHA256 hex digest of the JSON body");
149931
+ lines.push(" - `X-Webhook-Timestamp`: ISO 8601 timestamp");
149932
+ lines.push(" - `X-Webhook-Event`: Event type name");
149933
+ return lines.join(`
149934
+ `);
149935
+ }
149936
+ return `Error: ${result.message}`;
149937
+ },
149938
+ webhook_list: async () => {
149939
+ const manager = getWebhooksManager();
149940
+ if (!manager) {
149941
+ return "Error: Webhooks are not enabled or configured.";
149942
+ }
149943
+ try {
149944
+ const webhooks = await manager.list();
149945
+ if (webhooks.length === 0) {
149946
+ return "No webhooks registered. Use webhook_create to create one.";
149947
+ }
149948
+ const lines = [];
149949
+ lines.push(`## Webhooks (${webhooks.length})`);
149950
+ lines.push("");
149951
+ for (const wh of webhooks) {
149952
+ const statusIcon = wh.status === "active" ? "\u25CF" : wh.status === "paused" ? "\u25D0" : "\u2717";
149953
+ const statusColor = wh.status === "active" ? "" : ` [${wh.status}]`;
149954
+ const lastDelivery = wh.lastDeliveryAt ? new Date(wh.lastDeliveryAt).toLocaleDateString() : "never";
149955
+ lines.push(`${statusIcon} **${wh.name}** (${wh.id})${statusColor}`);
149956
+ lines.push(` Source: ${wh.source} | Events: ${wh.deliveryCount} | Last: ${lastDelivery}`);
149957
+ lines.push("");
149958
+ }
149959
+ return lines.join(`
149960
+ `);
149961
+ } catch (error2) {
149962
+ return `Error listing webhooks: ${error2 instanceof Error ? error2.message : String(error2)}`;
149963
+ }
149964
+ },
149965
+ webhook_get: async (input) => {
149966
+ const manager = getWebhooksManager();
149967
+ if (!manager) {
149968
+ return "Error: Webhooks are not enabled or configured.";
149969
+ }
149970
+ const id = String(input.id || "").trim();
149971
+ if (!id)
149972
+ return "Error: Webhook ID is required.";
149973
+ try {
149974
+ const webhook = await manager.get(id);
149975
+ if (!webhook) {
149976
+ return `Webhook ${id} not found.`;
149977
+ }
149978
+ const lines = [];
149979
+ lines.push(`## Webhook: ${webhook.name}`);
149980
+ lines.push("");
149981
+ lines.push(`**ID:** ${webhook.id}`);
149982
+ lines.push(`**Source:** ${webhook.source}`);
149983
+ lines.push(`**Status:** ${webhook.status}`);
149984
+ if (webhook.description) {
149985
+ lines.push(`**Description:** ${webhook.description}`);
149986
+ }
149987
+ lines.push(`**Secret:** ${webhook.secret}`);
149988
+ lines.push(`**URL:** /api/v1/webhooks/receive/${webhook.id}`);
149989
+ lines.push(`**Events Filter:** ${webhook.eventsFilter.length > 0 ? webhook.eventsFilter.join(", ") : "all"}`);
149990
+ lines.push(`**Deliveries:** ${webhook.deliveryCount}`);
149991
+ lines.push(`**Created:** ${new Date(webhook.createdAt).toLocaleString()}`);
149992
+ if (webhook.lastDeliveryAt) {
149993
+ lines.push(`**Last Delivery:** ${new Date(webhook.lastDeliveryAt).toLocaleString()}`);
149994
+ }
149995
+ return lines.join(`
149996
+ `);
149997
+ } catch (error2) {
149998
+ return `Error getting webhook: ${error2 instanceof Error ? error2.message : String(error2)}`;
149999
+ }
150000
+ },
150001
+ webhook_update: async (input) => {
150002
+ const manager = getWebhooksManager();
150003
+ if (!manager) {
150004
+ return "Error: Webhooks are not enabled or configured.";
150005
+ }
150006
+ const id = String(input.id || "").trim();
150007
+ if (!id)
150008
+ return "Error: Webhook ID is required.";
150009
+ const name2 = input.name ? String(input.name).trim() : undefined;
150010
+ const description = input.description ? String(input.description).trim() : undefined;
150011
+ const eventsFilter = Array.isArray(input.eventsFilter) ? input.eventsFilter.map(String) : undefined;
150012
+ const status = input.status;
150013
+ const result = await manager.update({ id, name: name2, description, eventsFilter, status });
150014
+ return result.message;
150015
+ },
150016
+ webhook_delete: async (input) => {
150017
+ const manager = getWebhooksManager();
150018
+ if (!manager) {
150019
+ return "Error: Webhooks are not enabled or configured.";
150020
+ }
150021
+ const id = String(input.id || "").trim();
150022
+ if (!id)
150023
+ return "Error: Webhook ID is required.";
150024
+ const result = await manager.delete(id);
150025
+ return result.message;
150026
+ },
150027
+ webhook_events: async (input) => {
150028
+ const manager = getWebhooksManager();
150029
+ if (!manager) {
150030
+ return "Error: Webhooks are not enabled or configured.";
150031
+ }
150032
+ const webhookId = String(input.webhookId || "").trim();
150033
+ if (!webhookId)
150034
+ return "Error: Webhook ID is required.";
150035
+ const limit2 = typeof input.limit === "number" ? input.limit : 20;
150036
+ const pendingOnly = input.pendingOnly === true;
150037
+ try {
150038
+ const events = await manager.listEvents(webhookId, { limit: limit2, pendingOnly });
150039
+ if (events.length === 0) {
150040
+ return pendingOnly ? "No pending events for this webhook." : "No events received for this webhook.";
150041
+ }
150042
+ const lines = [];
150043
+ lines.push(`## Events for ${webhookId} (${events.length})`);
150044
+ lines.push("");
150045
+ for (const evt of events) {
150046
+ const statusIcon = evt.status === "pending" ? "\u23F3" : evt.status === "injected" ? "\uD83D\uDCE8" : evt.status === "processed" ? "\u2713" : "\u2717";
150047
+ const date = new Date(evt.timestamp).toLocaleString();
150048
+ lines.push(`${statusIcon} **${evt.eventType}** (${evt.id})`);
150049
+ lines.push(` Source: ${evt.source} | Status: ${evt.status} | ${date}`);
150050
+ lines.push(` Preview: ${evt.preview}`);
150051
+ lines.push("");
150052
+ }
150053
+ return lines.join(`
150054
+ `);
150055
+ } catch (error2) {
150056
+ return `Error listing events: ${error2 instanceof Error ? error2.message : String(error2)}`;
150057
+ }
150058
+ },
150059
+ webhook_test: async (input) => {
150060
+ const manager = getWebhooksManager();
150061
+ if (!manager) {
150062
+ return "Error: Webhooks are not enabled or configured.";
150063
+ }
150064
+ const id = String(input.id || "").trim();
150065
+ if (!id)
150066
+ return "Error: Webhook ID is required.";
150067
+ const result = await manager.sendTestEvent(id);
150068
+ if (result.success) {
150069
+ return `Test event sent successfully! Event ID: ${result.eventId}, Delivery ID: ${result.deliveryId}`;
150070
+ }
150071
+ return `Error: ${result.message}`;
150072
+ }
150073
+ };
150074
+ }
150075
+ var webhookTools = [
150076
+ webhookCreateTool,
150077
+ webhookListTool,
150078
+ webhookGetTool,
150079
+ webhookUpdateTool,
150080
+ webhookDeleteTool,
150081
+ webhookEventsTool,
150082
+ webhookTestTool
150083
+ ];
150084
+ function registerWebhookTools(registry2, getWebhooksManager) {
150085
+ const executors = createWebhookToolExecutors(getWebhooksManager);
150086
+ for (const tool of webhookTools) {
150087
+ registry2.register(tool, executors[tool.name]);
150088
+ }
150089
+ }
150090
+ // ../core/src/sessions/store.ts
150091
+ import { join as join43 } from "path";
150092
+ import { homedir as homedir21 } from "os";
150093
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14, readFileSync as readFileSync17, readdirSync as readdirSync9, unlinkSync as unlinkSync5 } from "fs";
148291
150094
 
148292
150095
  class SessionStore {
148293
150096
  basePath;
148294
150097
  constructor(basePath) {
148295
- const envHome = process.env.HOME || process.env.USERPROFILE || homedir20();
148296
- this.basePath = basePath || join40(envHome, ".assistants", "sessions");
150098
+ const envHome = process.env.HOME || process.env.USERPROFILE || homedir21();
150099
+ this.basePath = basePath || join43(envHome, ".assistants", "sessions");
148297
150100
  this.ensureDir();
148298
150101
  }
148299
150102
  ensureDir() {
148300
- if (!existsSync24(this.basePath)) {
148301
- mkdirSync15(this.basePath, { recursive: true });
150103
+ if (!existsSync26(this.basePath)) {
150104
+ mkdirSync16(this.basePath, { recursive: true });
148302
150105
  }
148303
150106
  }
148304
150107
  getSessionPath(id) {
148305
- return join40(this.basePath, `${id}.json`);
150108
+ return join43(this.basePath, `${id}.json`);
148306
150109
  }
148307
150110
  save(data) {
148308
150111
  try {
@@ -148313,7 +150116,7 @@ class SessionStore {
148313
150116
  load(id) {
148314
150117
  try {
148315
150118
  const filePath = this.getSessionPath(id);
148316
- if (!existsSync24(filePath))
150119
+ if (!existsSync26(filePath))
148317
150120
  return null;
148318
150121
  return JSON.parse(readFileSync17(filePath, "utf-8"));
148319
150122
  } catch {
@@ -148323,11 +150126,11 @@ class SessionStore {
148323
150126
  list() {
148324
150127
  try {
148325
150128
  this.ensureDir();
148326
- const files = readdirSync7(this.basePath).filter((f6) => f6.endsWith(".json"));
150129
+ const files = readdirSync9(this.basePath).filter((f6) => f6.endsWith(".json"));
148327
150130
  const sessions = [];
148328
150131
  for (const file of files) {
148329
150132
  try {
148330
- const data = JSON.parse(readFileSync17(join40(this.basePath, file), "utf-8"));
150133
+ const data = JSON.parse(readFileSync17(join43(this.basePath, file), "utf-8"));
148331
150134
  sessions.push(data);
148332
150135
  } catch {}
148333
150136
  }
@@ -148339,7 +150142,7 @@ class SessionStore {
148339
150142
  delete(id) {
148340
150143
  try {
148341
150144
  const filePath = this.getSessionPath(id);
148342
- if (existsSync24(filePath)) {
150145
+ if (existsSync26(filePath)) {
148343
150146
  unlinkSync5(filePath);
148344
150147
  }
148345
150148
  } catch {}
@@ -148387,6 +150190,7 @@ class EmbeddedClient {
148387
150190
  this.assistantLoop = createAssistant({
148388
150191
  cwd: this.cwd,
148389
150192
  sessionId,
150193
+ assistantId: options?.assistantId,
148390
150194
  allowedTools: options?.allowedTools,
148391
150195
  extraSystemPrompt: options?.systemPrompt,
148392
150196
  model: options?.model,
@@ -148637,6 +150441,12 @@ class EmbeddedClient {
148637
150441
  }
148638
150442
  return null;
148639
150443
  }
150444
+ getWebhooksManager() {
150445
+ if (typeof this.assistantLoop.getWebhooksManager === "function") {
150446
+ return this.assistantLoop.getWebhooksManager();
150447
+ }
150448
+ return null;
150449
+ }
148640
150450
  getWalletManager() {
148641
150451
  if (typeof this.assistantLoop.getWalletManager === "function") {
148642
150452
  return this.assistantLoop.getWalletManager();
@@ -148748,17 +150558,25 @@ class SessionRegistry {
148748
150558
  maxBufferedChunks = 2000;
148749
150559
  store;
148750
150560
  constructor(clientFactory) {
148751
- this.clientFactory = clientFactory ?? ((cwd) => new EmbeddedClient(cwd));
150561
+ this.clientFactory = clientFactory ?? ((cwd, options) => new EmbeddedClient(cwd, options));
148752
150562
  this.store = new SessionStore;
148753
150563
  }
148754
150564
  async createSession(cwdOrOptions) {
148755
150565
  const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
148756
- const client = this.clientFactory(options.cwd);
150566
+ const clientOptions = {
150567
+ sessionId: options.sessionId,
150568
+ initialMessages: options.initialMessages,
150569
+ startedAt: options.startedAt,
150570
+ assistantId: options.assistantId
150571
+ };
150572
+ const client = this.clientFactory(options.cwd, clientOptions);
148757
150573
  await client.initialize();
150574
+ const parsedStartedAt = options.startedAt ? new Date(options.startedAt).getTime() : Date.now();
150575
+ const startedAt = Number.isNaN(parsedStartedAt) ? Date.now() : parsedStartedAt;
148758
150576
  const sessionInfo = {
148759
150577
  id: client.getSessionId(),
148760
150578
  cwd: options.cwd,
148761
- startedAt: Date.now(),
150579
+ startedAt,
148762
150580
  updatedAt: Date.now(),
148763
150581
  isProcessing: false,
148764
150582
  client,
@@ -150497,9 +152315,9 @@ function createSelfAwarenessToolExecutors(context) {
150497
152315
  return JSON.stringify(response, null, 2);
150498
152316
  },
150499
152317
  workspace_map: async (input) => {
150500
- const { readdir: readdir6, stat: stat5, access } = await import("fs/promises");
152318
+ const { readdir: readdir7, stat: stat5, access } = await import("fs/promises");
150501
152319
  const { execSync } = await import("child_process");
150502
- const { join: join41, basename: basename6 } = await import("path");
152320
+ const { join: join44, basename: basename6 } = await import("path");
150503
152321
  const depth = input.depth ?? 3;
150504
152322
  const includeGitStatus = input.include_git_status !== false;
150505
152323
  const includeRecentFiles = input.include_recent_files !== false;
@@ -150534,7 +152352,7 @@ function createSelfAwarenessToolExecutors(context) {
150534
152352
  if (currentDepth > depth)
150535
152353
  return [];
150536
152354
  try {
150537
- const entries = await readdir6(dir, { withFileTypes: true });
152355
+ const entries = await readdir7(dir, { withFileTypes: true });
150538
152356
  const nodes = [];
150539
152357
  let fileCount = 0;
150540
152358
  for (const entry of entries) {
@@ -150545,7 +152363,7 @@ function createSelfAwarenessToolExecutors(context) {
150545
152363
  break;
150546
152364
  }
150547
152365
  if (entry.isDirectory()) {
150548
- const children = await buildTree(join41(dir, entry.name), currentDepth + 1);
152366
+ const children = await buildTree(join44(dir, entry.name), currentDepth + 1);
150549
152367
  nodes.push({
150550
152368
  name: entry.name,
150551
152369
  type: "directory",
@@ -150571,7 +152389,7 @@ function createSelfAwarenessToolExecutors(context) {
150571
152389
  };
150572
152390
  if (includeGitStatus) {
150573
152391
  try {
150574
- await access(join41(cwd, ".git"));
152392
+ await access(join44(cwd, ".git"));
150575
152393
  gitStatus.isRepo = true;
150576
152394
  try {
150577
152395
  const branch = execSync("git branch --show-current", { cwd, encoding: "utf-8" }).trim();
@@ -150598,11 +152416,11 @@ function createSelfAwarenessToolExecutors(context) {
150598
152416
  if (recentFiles.length >= 10)
150599
152417
  return;
150600
152418
  try {
150601
- const entries = await readdir6(dir, { withFileTypes: true });
152419
+ const entries = await readdir7(dir, { withFileTypes: true });
150602
152420
  for (const entry of entries) {
150603
152421
  if (shouldIgnore(entry.name))
150604
152422
  continue;
150605
- const fullPath = join41(dir, entry.name);
152423
+ const fullPath = join44(dir, entry.name);
150606
152424
  const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
150607
152425
  if (entry.isFile()) {
150608
152426
  try {
@@ -150636,12 +152454,12 @@ function createSelfAwarenessToolExecutors(context) {
150636
152454
  let projectName = basename6(cwd);
150637
152455
  for (const indicator of projectIndicators) {
150638
152456
  try {
150639
- await access(join41(cwd, indicator.file));
152457
+ await access(join44(cwd, indicator.file));
150640
152458
  projectType = indicator.type;
150641
152459
  if (indicator.file === "package.json") {
150642
152460
  try {
150643
- const { readFile: readFile15 } = await import("fs/promises");
150644
- const pkg = JSON.parse(await readFile15(join41(cwd, indicator.file), "utf-8"));
152461
+ const { readFile: readFile16 } = await import("fs/promises");
152462
+ const pkg = JSON.parse(await readFile16(join44(cwd, indicator.file), "utf-8"));
150645
152463
  projectName = pkg.name || projectName;
150646
152464
  } catch {}
150647
152465
  }
@@ -153203,8 +155021,8 @@ await __promiseAll([
153203
155021
  init_config(),
153204
155022
  init_runtime()
153205
155023
  ]);
153206
- import { join as join41, dirname as dirname17 } from "path";
153207
- import { existsSync as existsSync25, mkdirSync as mkdirSync16 } from "fs";
155024
+ import { join as join44, dirname as dirname18 } from "path";
155025
+ import { existsSync as existsSync27, mkdirSync as mkdirSync17 } from "fs";
153208
155026
  var MAX_KEY_LENGTH2 = 256;
153209
155027
  var MAX_VALUE_SIZE = 65536;
153210
155028
  var MAX_SUMMARY_LENGTH2 = 500;
@@ -153217,10 +155035,10 @@ class GlobalMemoryManager {
153217
155035
  config;
153218
155036
  constructor(options = {}) {
153219
155037
  const baseDir = getConfigDir();
153220
- const path2 = options.dbPath || join41(baseDir, "memory.db");
153221
- const dir = dirname17(path2);
153222
- if (!existsSync25(dir)) {
153223
- mkdirSync16(dir, { recursive: true });
155038
+ const path2 = options.dbPath || join44(baseDir, "memory.db");
155039
+ const dir = dirname18(path2);
155040
+ if (!existsSync27(dir)) {
155041
+ mkdirSync17(dir, { recursive: true });
153224
155042
  }
153225
155043
  const runtime = getRuntime();
153226
155044
  this.db = runtime.openDatabase(path2);
@@ -154623,9 +156441,9 @@ function createCapabilityChain(scope, capabilities) {
154623
156441
  };
154624
156442
  }
154625
156443
  // ../core/src/capabilities/storage.ts
154626
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
154627
- import { join as join42, dirname as dirname18 } from "path";
154628
- import { homedir as homedir21 } from "os";
156444
+ import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
156445
+ import { join as join45, dirname as dirname19 } from "path";
156446
+ import { homedir as homedir22 } from "os";
154629
156447
  var DEFAULT_STORAGE_CONFIG = {
154630
156448
  enabled: true,
154631
156449
  autoSave: true
@@ -154644,15 +156462,15 @@ class CapabilityStorage {
154644
156462
  if (this.config.storagePath) {
154645
156463
  return this.config.storagePath;
154646
156464
  }
154647
- const home = process.env.HOME || process.env.USERPROFILE || homedir21();
154648
- return join42(home, ".assistants", "capabilities", "store.json");
156465
+ const home = process.env.HOME || process.env.USERPROFILE || homedir22();
156466
+ return join45(home, ".assistants", "capabilities", "store.json");
154649
156467
  }
154650
156468
  load() {
154651
156469
  if (!this.config.enabled)
154652
156470
  return;
154653
156471
  try {
154654
156472
  const path2 = this.getStoragePath();
154655
- if (!existsSync26(path2))
156473
+ if (!existsSync28(path2))
154656
156474
  return;
154657
156475
  const data = JSON.parse(readFileSync18(path2, "utf-8"));
154658
156476
  if (data.chains) {
@@ -154672,9 +156490,9 @@ class CapabilityStorage {
154672
156490
  return;
154673
156491
  try {
154674
156492
  const path2 = this.getStoragePath();
154675
- const dir = dirname18(path2);
154676
- if (!existsSync26(dir)) {
154677
- mkdirSync17(dir, { recursive: true });
156493
+ const dir = dirname19(path2);
156494
+ if (!existsSync28(dir)) {
156495
+ mkdirSync18(dir, { recursive: true });
154678
156496
  }
154679
156497
  const data = {
154680
156498
  version: 1,
@@ -155016,6 +156834,7 @@ class AssistantLoop {
155016
156834
  heartbeatManager = null;
155017
156835
  heartbeatPersistence = null;
155018
156836
  heartbeatRecovery = null;
156837
+ heartbeatRuntimeConfig = null;
155019
156838
  lastUserMessage = null;
155020
156839
  lastToolName = null;
155021
156840
  pendingToolCalls = new Map;
@@ -155057,6 +156876,7 @@ class AssistantLoop {
155057
156876
  secretsManager = null;
155058
156877
  jobManager = null;
155059
156878
  messagesManager = null;
156879
+ webhooksManager = null;
155060
156880
  memoryManager = null;
155061
156881
  memoryInjector = null;
155062
156882
  contextInjector = null;
@@ -155064,6 +156884,7 @@ class AssistantLoop {
155064
156884
  subassistantManager = null;
155065
156885
  depth = 0;
155066
156886
  pendingMessagesContext = null;
156887
+ pendingWebhooksContext = null;
155067
156888
  pendingMemoryContext = null;
155068
156889
  identityContext = null;
155069
156890
  projectContext = null;
@@ -155272,6 +157093,20 @@ class AssistantLoop {
155272
157093
  }
155273
157094
  });
155274
157095
  }
157096
+ if (this.config?.webhooks?.enabled) {
157097
+ const assistant = this.assistantManager?.getActive();
157098
+ const assistantId = assistant?.id || this.sessionId;
157099
+ this.webhooksManager = createWebhooksManager(assistantId, this.config.webhooks);
157100
+ await this.webhooksManager.initialize();
157101
+ registerWebhookTools(this.toolRegistry, () => this.webhooksManager);
157102
+ this.webhooksManager.startWatching();
157103
+ this.webhooksManager.onEvent((event) => {
157104
+ const context = this.webhooksManager.buildInjectionContext([event]);
157105
+ if (context) {
157106
+ this.pendingWebhooksContext = context;
157107
+ }
157108
+ });
157109
+ }
155275
157110
  const memoryConfig = this.config?.memory;
155276
157111
  if (memoryConfig?.enabled !== false) {
155277
157112
  const assistant = this.assistantManager?.getActive();
@@ -155374,6 +157209,11 @@ class AssistantLoop {
155374
157209
  }
155375
157210
  }
155376
157211
  });
157212
+ registerHeartbeatTools(this.toolRegistry, {
157213
+ sessionId: this.sessionId,
157214
+ getHeartbeatState: () => this.getHeartbeatState(),
157215
+ getHeartbeatConfig: () => this.heartbeatRuntimeConfig
157216
+ });
155377
157217
  registerContextEntryTools(this.toolRegistry, {
155378
157218
  cwd: this.cwd,
155379
157219
  getActiveProjectId: () => this.activeProjectId,
@@ -155483,6 +157323,102 @@ class AssistantLoop {
155483
157323
  if (this.extraSystemPrompt) {
155484
157324
  this.context.addSystemMessage(this.extraSystemPrompt);
155485
157325
  }
157326
+ if (this.config?.heartbeat?.enabled !== false) {
157327
+ this.context.addSystemMessage(`## Heartbeat Basics
157328
+ - The system can trigger scheduled runs via \`/main-loop\` or \`/watchdog\`.
157329
+ - If invoked with those commands, follow the heartbeat skill instructions.
157330
+ - Heartbeat schedules use the id \`heartbeat-${this.sessionId}\`.`);
157331
+ }
157332
+ if (this.config?.heartbeat?.autonomous) {
157333
+ const maxSleepMin = Math.round((this.config.heartbeat.maxSleepMs ?? 1800000) / 60000);
157334
+ this.context.addSystemMessage(`## Autonomous Heartbeat System
157335
+
157336
+ You are running in **autonomous mode**. You manage your own wakeup schedule.
157337
+
157338
+ ### How it works
157339
+ - After every turn, a safety-net hook ensures a heartbeat schedule exists
157340
+ - The heartbeat fires \`/main-loop\` which runs your autonomous check-in skill
157341
+ - A watchdog monitors your health and forces a wakeup if you're overdue
157342
+
157343
+ ### Your responsibilities at the END of every turn
157344
+ 1. **Save state** to memory before the turn ends:
157345
+ - \`memory_save agent.heartbeat.intention "what you plan to do next"\`
157346
+ - \`memory_save agent.state.pending "items waiting for follow-up"\`
157347
+ - \`memory_save agent.state.lastActions "what you just did"\`
157348
+ 2. **Schedule your next heartbeat**:
157349
+ - Delete old: \`schedule_delete heartbeat-${this.sessionId}\`
157350
+ - Create new: \`schedule_create\` with \`kind: "once"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and \`at\` set to your chosen time
157351
+ 3. **Save goals** when they change: \`memory_save agent.goals "..."\`
157352
+
157353
+ ### Timing guidelines
157354
+ | Situation | Wake up in |
157355
+ |-----------|-----------|
157356
+ | Active jobs running or tasks pending | 1\u20133 minutes |
157357
+ | Goals exist but nothing urgent | 5\u201315 minutes |
157358
+ | Nothing pending, user idle | 15\u2013${maxSleepMin} minutes (max) |
157359
+
157360
+ ### Key memory keys
157361
+ - \`agent.heartbeat.last\` \u2014 when you last ran (save ISO timestamp)
157362
+ - \`agent.heartbeat.next\` \u2014 when you plan to run next
157363
+ - \`agent.heartbeat.intention\` \u2014 why you're waking up
157364
+ - \`agent.goals\` \u2014 your active goals
157365
+ - \`agent.state.pending\` \u2014 items waiting
157366
+ - \`agent.state.lastActions\` \u2014 what you did recently
157367
+
157368
+ ### Rules
157369
+ - **Stay fast** \u2014 if work takes >30s, delegate to a subassistant
157370
+ - **Never sleep longer than ${maxSleepMin} minutes** \u2014 the system enforces this cap
157371
+ - **Always schedule your next heartbeat** \u2014 if you forget, the safety net creates a default one
157372
+ `);
157373
+ }
157374
+ if (this.config.heartbeat?.enabled !== false) {
157375
+ this.context.addSystemMessage(`## Heartbeat Basics
157376
+ - The system can trigger scheduled runs via \`/main-loop\` or \`/watchdog\`.
157377
+ - If invoked with those commands, follow the heartbeat skill instructions.
157378
+ - Heartbeat schedules use the id \`heartbeat-${this.sessionId}\`.`);
157379
+ }
157380
+ if (this.config.heartbeat?.autonomous) {
157381
+ const maxSleepMin = Math.round((this.config.heartbeat.maxSleepMs ?? 1800000) / 60000);
157382
+ this.context.addSystemMessage(`## Autonomous Heartbeat System
157383
+
157384
+ You are running in **autonomous mode**. You manage your own wakeup schedule.
157385
+
157386
+ ### How it works
157387
+ - After every turn, a safety-net hook ensures a heartbeat schedule exists
157388
+ - The heartbeat fires \`/main-loop\` which runs your autonomous check-in skill
157389
+ - A watchdog monitors your health and forces a wakeup if you're overdue
157390
+
157391
+ ### Your responsibilities at the END of every turn
157392
+ 1. **Save state** to memory before the turn ends:
157393
+ - \`memory_save agent.heartbeat.intention "what you plan to do next"\`
157394
+ - \`memory_save agent.state.pending "items waiting for follow-up"\`
157395
+ - \`memory_save agent.state.lastActions "what you just did"\`
157396
+ 2. **Schedule your next heartbeat**:
157397
+ - Delete old: \`schedule_delete heartbeat-${this.sessionId}\`
157398
+ - Create new: \`schedule_create\` with \`kind: "once"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and \`at\` set to your chosen time
157399
+ 3. **Save goals** when they change: \`memory_save agent.goals "..."\`
157400
+
157401
+ ### Timing guidelines
157402
+ | Situation | Wake up in |
157403
+ |-----------|-----------|
157404
+ | Active jobs running or tasks pending | 1\u20133 minutes |
157405
+ | Goals exist but nothing urgent | 5\u201315 minutes |
157406
+ | Nothing pending, user idle | 15\u2013${maxSleepMin} minutes (max) |
157407
+
157408
+ ### Key memory keys
157409
+ - \`agent.heartbeat.last\` \u2014 when you last ran (save ISO timestamp)
157410
+ - \`agent.heartbeat.next\` \u2014 when you plan to run next
157411
+ - \`agent.heartbeat.intention\` \u2014 why you're waking up
157412
+ - \`agent.goals\` \u2014 your active goals
157413
+ - \`agent.state.pending\` \u2014 items waiting
157414
+ - \`agent.state.lastActions\` \u2014 what you did recently
157415
+
157416
+ ### Rules
157417
+ - **Stay fast** \u2014 if work takes >30s, delegate to a subassistant
157418
+ - **Never sleep longer than ${maxSleepMin} minutes** \u2014 the system enforces this cap
157419
+ - **Always schedule your next heartbeat** \u2014 if you forget, the safety net creates a default one
157420
+ `);
157421
+ }
155486
157422
  this.contextManager?.refreshState(this.context.getMessages());
155487
157423
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
155488
157424
  session_id: this.sessionId,
@@ -155500,6 +157436,7 @@ class AssistantLoop {
155500
157436
  this.isRunning = true;
155501
157437
  try {
155502
157438
  await this.injectPendingMessages();
157439
+ await this.injectPendingWebhookEvents();
155503
157440
  await this.injectMemoryContext(userMessage);
155504
157441
  await this.injectContextInfo();
155505
157442
  } catch (error2) {
@@ -156216,6 +158153,7 @@ class AssistantLoop {
156216
158153
  getWalletManager: () => this.walletManager,
156217
158154
  getSecretsManager: () => this.secretsManager,
156218
158155
  getMessagesManager: () => this.messagesManager,
158156
+ getWebhooksManager: () => this.webhooksManager,
156219
158157
  getMemoryManager: () => this.memoryManager,
156220
158158
  refreshIdentityContext: async () => {
156221
158159
  if (this.identityManager) {
@@ -156242,6 +158180,8 @@ class AssistantLoop {
156242
158180
  this.setProjectContext(content);
156243
158181
  },
156244
158182
  getVoiceState: () => this.getVoiceState(),
158183
+ getHeartbeatState: () => this.getHeartbeatState(),
158184
+ getHeartbeatConfig: () => this.heartbeatRuntimeConfig,
156245
158185
  enableVoice: () => {
156246
158186
  if (!this.voiceManager) {
156247
158187
  throw new Error("Voice support is not available.");
@@ -156452,6 +158392,7 @@ class AssistantLoop {
156452
158392
  this.voiceManager?.stopSpeaking();
156453
158393
  this.voiceManager?.stopListening();
156454
158394
  this.messagesManager?.stopWatching();
158395
+ this.webhooksManager?.stopWatching();
156455
158396
  this.memoryManager?.close();
156456
158397
  this.memoryManager = null;
156457
158398
  this.memoryInjector = null;
@@ -156499,12 +158440,16 @@ class AssistantLoop {
156499
158440
  const lastActivity = this.heartbeatManager.getLastActivity();
156500
158441
  const age = Date.now() - lastActivity;
156501
158442
  const stats = this.heartbeatManager.getStats();
158443
+ const intervalMs = this.heartbeatRuntimeConfig?.intervalMs ?? this.config?.heartbeat?.intervalMs ?? 15000;
158444
+ const nextHeartbeatAt = this.heartbeatManager.getNextHeartbeatAt();
156502
158445
  return {
156503
158446
  enabled: true,
156504
158447
  state: this.heartbeatManager.getState(),
156505
158448
  lastActivity: new Date(lastActivity).toISOString(),
156506
158449
  uptimeSeconds: stats.uptimeSeconds,
156507
- isStale: age > staleThresholdMs
158450
+ isStale: age > staleThresholdMs,
158451
+ intervalMs,
158452
+ nextHeartbeatAt: new Date(nextHeartbeatAt).toISOString()
156508
158453
  };
156509
158454
  }
156510
158455
  getAssistantManager() {
@@ -156516,6 +158461,9 @@ class AssistantLoop {
156516
158461
  getMessagesManager() {
156517
158462
  return this.messagesManager;
156518
158463
  }
158464
+ getWebhooksManager() {
158465
+ return this.webhooksManager;
158466
+ }
156519
158467
  getWalletManager() {
156520
158468
  return this.walletManager;
156521
158469
  }
@@ -156747,7 +158695,8 @@ ${content.trim()}`);
156747
158695
  const heartbeatConfig = this.buildHeartbeatConfig(this.config);
156748
158696
  if (!heartbeatConfig)
156749
158697
  return;
156750
- const statePath = join43(getConfigDir(), "state", `${this.sessionId}.json`);
158698
+ this.heartbeatRuntimeConfig = heartbeatConfig;
158699
+ const statePath = join46(getConfigDir(), "state", `${this.sessionId}.json`);
156751
158700
  this.heartbeatManager = new HeartbeatManager(heartbeatConfig);
156752
158701
  this.heartbeatPersistence = new StatePersistence(statePath);
156753
158702
  this.heartbeatRecovery = new RecoveryManager(this.heartbeatPersistence, heartbeatConfig.persistPath, heartbeatConfig.staleThresholdMs, {
@@ -156868,7 +158817,7 @@ ${content.trim()}`);
156868
158817
  async startEnergySystem() {
156869
158818
  if (!this.config || this.config.energy?.enabled === false)
156870
158819
  return;
156871
- const statePath = join43(getConfigDir(), "energy", "state.json");
158820
+ const statePath = join46(getConfigDir(), "energy", "state.json");
156872
158821
  this.energyManager = new EnergyManager(this.config.energy, new EnergyStorage(statePath));
156873
158822
  await this.energyManager.initialize();
156874
158823
  this.refreshEnergyEffects();
@@ -156929,6 +158878,29 @@ ${effects.message}
156929
158878
  this.pendingMessagesContext = null;
156930
158879
  }
156931
158880
  }
158881
+ async injectPendingWebhookEvents() {
158882
+ if (!this.webhooksManager)
158883
+ return;
158884
+ try {
158885
+ if (this.pendingWebhooksContext) {
158886
+ const previous = this.pendingWebhooksContext.trim();
158887
+ this.context.removeSystemMessages((content) => content.trim() === previous);
158888
+ this.pendingWebhooksContext = null;
158889
+ }
158890
+ const pending = await this.webhooksManager.getPendingForInjection();
158891
+ if (pending.length === 0) {
158892
+ return;
158893
+ }
158894
+ this.pendingWebhooksContext = this.webhooksManager.buildInjectionContext(pending);
158895
+ if (this.pendingWebhooksContext) {
158896
+ this.context.addSystemMessage(this.pendingWebhooksContext);
158897
+ }
158898
+ await this.webhooksManager.markInjected(pending.map((e6) => ({ webhookId: e6.webhookId, eventId: e6.id })));
158899
+ } catch (error2) {
158900
+ console.error("Failed to inject pending webhook events:", error2);
158901
+ this.pendingWebhooksContext = null;
158902
+ }
158903
+ }
156932
158904
  async injectMemoryContext(userMessage) {
156933
158905
  if (!this.memoryInjector || !this.memoryInjector.isEnabled())
156934
158906
  return;
@@ -157198,11 +159170,13 @@ ${this.identityContext}`);
157198
159170
  return null;
157199
159171
  const intervalMs = Math.max(1000, config.heartbeat?.intervalMs ?? 15000);
157200
159172
  const staleThresholdMs = Math.max(intervalMs * 2, config.heartbeat?.staleThresholdMs ?? 120000);
157201
- const persistPath = config.heartbeat?.persistPath ?? join43(getConfigDir(), "heartbeats", `${this.sessionId}.json`);
159173
+ const persistPath = config.heartbeat?.persistPath ?? join46(getConfigDir(), "heartbeats", `${this.sessionId}.json`);
159174
+ const historyPath = config.heartbeat?.historyPath ?? join46(getConfigDir(), "heartbeats", "runs", `${this.sessionId}.jsonl`);
157202
159175
  return {
157203
159176
  intervalMs,
157204
159177
  staleThresholdMs,
157205
- persistPath
159178
+ persistPath,
159179
+ historyPath
157206
159180
  };
157207
159181
  }
157208
159182
  async buildSummaryClient(contextConfig) {
@@ -157586,9 +159560,9 @@ class StatsTracker {
157586
159560
  }
157587
159561
  }
157588
159562
  // ../core/src/tools/connector-index.ts
157589
- import { join as join44, dirname as dirname19 } from "path";
157590
- import { homedir as homedir22 } from "os";
157591
- import { existsSync as existsSync27, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16, readFileSync as readFileSync19 } from "fs";
159563
+ import { join as join47, dirname as dirname20 } from "path";
159564
+ import { homedir as homedir23 } from "os";
159565
+ import { existsSync as existsSync29, mkdirSync as mkdirSync19, writeFileSync as writeFileSync16, readFileSync as readFileSync19 } from "fs";
157592
159566
  var TAG_KEYWORDS = {
157593
159567
  email: ["email", "mail", "inbox", "send", "receive", "message", "compose"],
157594
159568
  calendar: ["calendar", "event", "meeting", "schedule", "appointment"],
@@ -157622,16 +159596,16 @@ class ConnectorIndex {
157622
159596
  }
157623
159597
  getHomeDir() {
157624
159598
  const envHome = process.env.HOME || process.env.USERPROFILE;
157625
- return envHome && envHome.trim().length > 0 ? envHome : homedir22();
159599
+ return envHome && envHome.trim().length > 0 ? envHome : homedir23();
157626
159600
  }
157627
159601
  getCachePath() {
157628
- return join44(this.getHomeDir(), ".assistants", "cache", "connector-index.json");
159602
+ return join47(this.getHomeDir(), ".assistants", "cache", "connector-index.json");
157629
159603
  }
157630
159604
  loadDiskCache() {
157631
159605
  ConnectorIndex.indexLoaded = true;
157632
159606
  try {
157633
159607
  const cachePath = this.getCachePath();
157634
- if (!existsSync27(cachePath))
159608
+ if (!existsSync29(cachePath))
157635
159609
  return;
157636
159610
  const data = JSON.parse(readFileSync19(cachePath, "utf-8"));
157637
159611
  if (data.version !== INDEX_VERSION)
@@ -157647,9 +159621,9 @@ class ConnectorIndex {
157647
159621
  saveDiskCache() {
157648
159622
  try {
157649
159623
  const cachePath = this.getCachePath();
157650
- const cacheDir = dirname19(cachePath);
157651
- if (!existsSync27(cacheDir)) {
157652
- mkdirSync18(cacheDir, { recursive: true });
159624
+ const cacheDir = dirname20(cachePath);
159625
+ if (!existsSync29(cacheDir)) {
159626
+ mkdirSync19(cacheDir, { recursive: true });
157653
159627
  }
157654
159628
  const data = {
157655
159629
  version: INDEX_VERSION,
@@ -158140,7 +160114,8 @@ async function runHeadless(options) {
158140
160114
  jsonSchema,
158141
160115
  continue: shouldContinue,
158142
160116
  resume,
158143
- cwdProvided
160117
+ cwdProvided,
160118
+ timeoutMs
158144
160119
  } = options;
158145
160120
  let sessionData = null;
158146
160121
  if (resume) {
@@ -158221,7 +160196,14 @@ IMPORTANT: Your response MUST be valid JSON conforming to this schema:
158221
160196
  ${jsonSchema}`;
158222
160197
  }
158223
160198
  try {
158224
- await client.send(message);
160199
+ if (timeoutMs && timeoutMs > 0) {
160200
+ await Promise.race([
160201
+ client.send(message),
160202
+ new Promise((_3, reject) => setTimeout(() => reject(new Error(`Headless run timed out after ${timeoutMs}ms`)), timeoutMs))
160203
+ ]);
160204
+ } else {
160205
+ await client.send(message);
160206
+ }
158225
160207
  } catch (error2) {
158226
160208
  hadError = true;
158227
160209
  errorMessage = error2 instanceof Error ? error2.message : String(error2);
@@ -158331,6 +160313,7 @@ function parseArgs(argv) {
158331
160313
  version: false,
158332
160314
  help: false,
158333
160315
  print: null,
160316
+ headlessTimeoutMs: null,
158334
160317
  outputFormat: "text",
158335
160318
  allowedTools: [],
158336
160319
  systemPrompt: null,
@@ -158419,6 +160402,21 @@ function parseArgs(argv) {
158419
160402
  }
158420
160403
  continue;
158421
160404
  }
160405
+ if (arg === "--headless-timeout" || arg === "--headless-timeout-ms") {
160406
+ const nextArg = args[i5 + 1];
160407
+ if (nextArg === undefined || isFlag(nextArg)) {
160408
+ options.errors.push(`${arg} requires a millisecond value`);
160409
+ } else {
160410
+ const parsed = Number(nextArg);
160411
+ if (!Number.isFinite(parsed) || parsed <= 0) {
160412
+ options.errors.push(`${arg} must be a positive number of milliseconds`);
160413
+ } else {
160414
+ options.headlessTimeoutMs = Math.floor(parsed);
160415
+ }
160416
+ i5++;
160417
+ }
160418
+ continue;
160419
+ }
158422
160420
  if (arg === "--continue" || arg === "-c") {
158423
160421
  options.continue = true;
158424
160422
  continue;