@ascenda-one/agent-mcp 0.1.8 → 0.1.9

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 (2) hide show
  1. package/dist/cli.js +562 -46
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -419,6 +419,93 @@ var require_out = __commonJS({
419
419
  }
420
420
  });
421
421
 
422
+ // ../packages/tool-kit/out/eventLog.js
423
+ var require_eventLog = __commonJS({
424
+ "../packages/tool-kit/out/eventLog.js"(exports) {
425
+ "use strict";
426
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
427
+ if (k2 === void 0) k2 = k;
428
+ var desc = Object.getOwnPropertyDescriptor(m, k);
429
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
430
+ desc = { enumerable: true, get: function() {
431
+ return m[k];
432
+ } };
433
+ }
434
+ Object.defineProperty(o, k2, desc);
435
+ } : function(o, m, k, k2) {
436
+ if (k2 === void 0) k2 = k;
437
+ o[k2] = m[k];
438
+ });
439
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
440
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
441
+ } : function(o, v) {
442
+ o["default"] = v;
443
+ });
444
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
445
+ var ownKeys = function(o) {
446
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
447
+ var ar = [];
448
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
449
+ return ar;
450
+ };
451
+ return ownKeys(o);
452
+ };
453
+ return function(mod) {
454
+ if (mod && mod.__esModule) return mod;
455
+ var result = {};
456
+ if (mod != null) {
457
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
458
+ }
459
+ __setModuleDefault(result, mod);
460
+ return result;
461
+ };
462
+ }();
463
+ Object.defineProperty(exports, "__esModule", { value: true });
464
+ exports.EVENT_LOG_ENV_VAR = void 0;
465
+ exports.expandUserPath = expandUserPath;
466
+ exports.resolveEventLogPath = resolveEventLogPath;
467
+ exports.appendEventLog = appendEventLog;
468
+ var fs = __importStar(__require("fs"));
469
+ var os = __importStar(__require("os"));
470
+ var path = __importStar(__require("path"));
471
+ exports.EVENT_LOG_ENV_VAR = "ASCENDA_EVENT_LOG_FILE";
472
+ var MAX_BYTES = 5 * 1024 * 1024;
473
+ function expandUserPath(configured) {
474
+ const value = configured?.trim();
475
+ if (!value)
476
+ return void 0;
477
+ if (value === "~")
478
+ return os.homedir();
479
+ if (value.startsWith("~/"))
480
+ return path.join(os.homedir(), value.slice(2));
481
+ return path.resolve(value);
482
+ }
483
+ function resolveEventLogPath() {
484
+ return expandUserPath(process.env[exports.EVENT_LOG_ENV_VAR]);
485
+ }
486
+ function appendEventLog(logFilePath, entry) {
487
+ try {
488
+ rotateIfLarge(logFilePath);
489
+ const dir = path.dirname(logFilePath);
490
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
491
+ fs.appendFileSync(logFilePath, `${JSON.stringify(entry)}
492
+ `, { encoding: "utf8", mode: 384 });
493
+ if (process.platform !== "win32")
494
+ fs.chmodSync(logFilePath, 384);
495
+ } catch {
496
+ }
497
+ }
498
+ function rotateIfLarge(logFilePath) {
499
+ try {
500
+ if (fs.statSync(logFilePath).size < MAX_BYTES)
501
+ return;
502
+ fs.renameSync(logFilePath, `${logFilePath}.1`);
503
+ } catch {
504
+ }
505
+ }
506
+ }
507
+ });
508
+
422
509
  // ../packages/tool-kit/out/http.js
423
510
  var require_http = __commonJS({
424
511
  "../packages/tool-kit/out/http.js"(exports) {
@@ -428,6 +515,7 @@ var require_http = __commonJS({
428
515
  exports.createPairingSession = createPairingSession;
429
516
  exports.getPairingStatus = getPairingStatus;
430
517
  exports.renewToolToken = renewToolToken;
518
+ exports.isRetryableStatus = isRetryableStatus;
431
519
  exports.postToolEvent = postToolEvent;
432
520
  exports.postToolEventsBatch = postToolEventsBatch;
433
521
  exports.parseIngestResponse = parseIngestResponse;
@@ -473,26 +561,34 @@ var require_http = __commonJS({
473
561
  throw new AscendaApiError(response.status, void 0, await response.text());
474
562
  return await response.json();
475
563
  }
564
+ function isRetryableStatus(status) {
565
+ return status === 408 || status === 429 || status !== void 0 && status >= 500 && status <= 599;
566
+ }
476
567
  async function postToolEvent(apiBaseUrl, eventWriteToken, payload, signal) {
477
- const response = await fetch(`${apiBaseUrl}/v1/tool-events`, {
478
- method: "POST",
479
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${eventWriteToken}` },
480
- body: JSON.stringify(payload),
481
- signal
482
- });
483
- return parseIngestResponse(response);
568
+ return sendIngest(`${apiBaseUrl}/v1/tool-events`, eventWriteToken, JSON.stringify(payload), signal);
484
569
  }
485
- async function postToolEventsBatch(apiBaseUrl, eventWriteToken, payloads) {
486
- const response = await fetch(`${apiBaseUrl}/v1/tool-events/batch`, {
487
- method: "POST",
488
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${eventWriteToken}` },
489
- body: JSON.stringify({ events: payloads })
490
- });
491
- return parseIngestResponse(response);
570
+ async function postToolEventsBatch(apiBaseUrl, eventWriteToken, payloads, signal) {
571
+ return sendIngest(`${apiBaseUrl}/v1/tool-events/batch`, eventWriteToken, JSON.stringify({ events: payloads }), signal);
572
+ }
573
+ async function sendIngest(url, eventWriteToken, body, signal) {
574
+ try {
575
+ const response = await fetch(url, {
576
+ method: "POST",
577
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${eventWriteToken}` },
578
+ body,
579
+ signal
580
+ });
581
+ return parseIngestResponse(response);
582
+ } catch (error2) {
583
+ return {
584
+ result: "transport_error",
585
+ detail: error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2)
586
+ };
587
+ }
492
588
  }
493
589
  async function parseIngestResponse(response) {
494
590
  if (response.ok)
495
- return "accepted";
591
+ return { result: "accepted", httpStatus: response.status };
496
592
  const body = await response.text();
497
593
  let errorCode;
498
594
  try {
@@ -500,13 +596,14 @@ var require_http = __commonJS({
500
596
  } catch {
501
597
  errorCode = void 0;
502
598
  }
599
+ const base = { httpStatus: response.status, errorCode, detail: body || void 0 };
503
600
  if (response.status === 401)
504
- return "auth_failed";
601
+ return { ...base, result: "auth_failed" };
505
602
  if (response.status === 403 && errorCode === "consent_missing_or_expired")
506
- return "consent_missing";
603
+ return { ...base, result: "consent_missing" };
507
604
  if (response.status === 400 || response.status === 422)
508
- return "validation_failed";
509
- throw new AscendaApiError(response.status, errorCode, body);
605
+ return { ...base, result: "validation_failed" };
606
+ return { ...base, result: "transport_error" };
510
607
  }
511
608
  }
512
609
  });
@@ -556,6 +653,7 @@ var require_tokenStore = __commonJS({
556
653
  exports.defaultTokenFilePath = defaultTokenFilePath2;
557
654
  exports.persistEventWriteToken = persistEventWriteToken2;
558
655
  exports.readTokenFile = readTokenFile2;
656
+ exports.sanitizeFilePart = sanitizeFilePart;
559
657
  var fs = __importStar(__require("fs"));
560
658
  var os = __importStar(__require("os"));
561
659
  var path = __importStar(__require("path"));
@@ -587,15 +685,144 @@ var require_tokenStore = __commonJS({
587
685
  }
588
686
  });
589
687
 
688
+ // ../packages/tool-kit/out/stateStore.js
689
+ var require_stateStore = __commonJS({
690
+ "../packages/tool-kit/out/stateStore.js"(exports) {
691
+ "use strict";
692
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
693
+ if (k2 === void 0) k2 = k;
694
+ var desc = Object.getOwnPropertyDescriptor(m, k);
695
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
696
+ desc = { enumerable: true, get: function() {
697
+ return m[k];
698
+ } };
699
+ }
700
+ Object.defineProperty(o, k2, desc);
701
+ } : function(o, m, k, k2) {
702
+ if (k2 === void 0) k2 = k;
703
+ o[k2] = m[k];
704
+ });
705
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
706
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
707
+ } : function(o, v) {
708
+ o["default"] = v;
709
+ });
710
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
711
+ var ownKeys = function(o) {
712
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
713
+ var ar = [];
714
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
715
+ return ar;
716
+ };
717
+ return ownKeys(o);
718
+ };
719
+ return function(mod) {
720
+ if (mod && mod.__esModule) return mod;
721
+ var result = {};
722
+ if (mod != null) {
723
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
724
+ }
725
+ __setModuleDefault(result, mod);
726
+ return result;
727
+ };
728
+ }();
729
+ Object.defineProperty(exports, "__esModule", { value: true });
730
+ exports.defaultStateFilePath = defaultStateFilePath;
731
+ exports.readCollectorState = readCollectorState;
732
+ exports.recordSendOutcome = recordSendOutcome;
733
+ exports.shouldAnnounceFailure = shouldAnnounceFailure;
734
+ exports.markFailureNotified = markFailureNotified;
735
+ var fs = __importStar(__require("fs"));
736
+ var os = __importStar(__require("os"));
737
+ var path = __importStar(__require("path"));
738
+ var tokenStore_1 = require_tokenStore();
739
+ function defaultStateFilePath(toolInstallationId) {
740
+ return path.join(os.homedir(), ".ascenda", "state", `${(0, tokenStore_1.sanitizeFilePart)(toolInstallationId)}.json`);
741
+ }
742
+ function readCollectorState(stateFilePath) {
743
+ try {
744
+ if (!fs.existsSync(stateFilePath))
745
+ return void 0;
746
+ const raw = fs.readFileSync(stateFilePath, "utf8").trim();
747
+ if (!raw)
748
+ return void 0;
749
+ const parsed = JSON.parse(raw);
750
+ if (!parsed || typeof parsed !== "object")
751
+ return void 0;
752
+ return parsed;
753
+ } catch {
754
+ return void 0;
755
+ }
756
+ }
757
+ function recordSendOutcome(stateFilePath, toolInstallationId, outcome, detail = {}) {
758
+ const now = (/* @__PURE__ */ new Date()).toISOString();
759
+ const previous = readCollectorState(stateFilePath);
760
+ const accepted = outcome === "accepted";
761
+ const next = {
762
+ toolInstallationId,
763
+ lastAttemptAt: now,
764
+ lastSuccessAt: accepted ? now : previous?.lastSuccessAt,
765
+ lastOutcome: outcome,
766
+ consecutiveFailures: accepted ? 0 : (previous?.consecutiveFailures ?? 0) + 1,
767
+ ...detail.httpStatus !== void 0 ? { httpStatus: detail.httpStatus } : {},
768
+ ...detail.errorCode !== void 0 ? { errorCode: detail.errorCode } : {},
769
+ ...detail.detail !== void 0 ? { detail: truncate(detail.detail) } : {},
770
+ // A success closes the episode; a failure either opens one or continues
771
+ // the one already open. Carrying `notifiedFailingSince` across a
772
+ // continuing episode is what keeps the notice to once per outage.
773
+ ...accepted ? {} : { failingSince: previous?.failingSince ?? now },
774
+ ...accepted ? {} : previous?.notifiedFailingSince !== void 0 ? { notifiedFailingSince: previous.notifiedFailingSince } : {}
775
+ };
776
+ writeStateFile(stateFilePath, next);
777
+ return next;
778
+ }
779
+ function shouldAnnounceFailure(state) {
780
+ if (!state || state.consecutiveFailures === 0 || !state.failingSince)
781
+ return false;
782
+ return state.notifiedFailingSince !== state.failingSince;
783
+ }
784
+ function markFailureNotified(stateFilePath, state) {
785
+ if (!state.failingSince)
786
+ return;
787
+ writeStateFile(stateFilePath, { ...state, notifiedFailingSince: state.failingSince });
788
+ }
789
+ function writeStateFile(stateFilePath, state) {
790
+ const temporaryPath = `${stateFilePath}.${process.pid}.tmp`;
791
+ try {
792
+ const dir = path.dirname(stateFilePath);
793
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
794
+ fs.writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}
795
+ `, { encoding: "utf8", mode: 384 });
796
+ fs.renameSync(temporaryPath, stateFilePath);
797
+ if (process.platform !== "win32")
798
+ fs.chmodSync(stateFilePath, 384);
799
+ } catch {
800
+ try {
801
+ if (fs.existsSync(temporaryPath))
802
+ fs.unlinkSync(temporaryPath);
803
+ } catch {
804
+ }
805
+ }
806
+ }
807
+ function truncate(value) {
808
+ const collapsed = value.replace(/\s+/g, " ").trim();
809
+ return collapsed.length > 200 ? `${collapsed.slice(0, 197)}...` : collapsed;
810
+ }
811
+ }
812
+ });
813
+
590
814
  // ../packages/tool-kit/out/eventSender.js
591
815
  var require_eventSender = __commonJS({
592
816
  "../packages/tool-kit/out/eventSender.js"(exports) {
593
817
  "use strict";
594
818
  Object.defineProperty(exports, "__esModule", { value: true });
595
819
  exports.AscendaEventSender = exports.AscendaSemanticEventError = void 0;
820
+ exports.buildEventPayload = buildEventPayload;
596
821
  var tool_contract_1 = require_out();
822
+ var eventLog_1 = require_eventLog();
597
823
  var http_1 = require_http();
598
824
  var tokenStore_1 = require_tokenStore();
825
+ var stateStore_1 = require_stateStore();
599
826
  var AscendaSemanticEventError = class extends Error {
600
827
  constructor(message) {
601
828
  super(message);
@@ -603,27 +830,32 @@ var require_eventSender = __commonJS({
603
830
  }
604
831
  };
605
832
  exports.AscendaSemanticEventError = AscendaSemanticEventError;
833
+ function buildEventPayload(identity, mapped) {
834
+ return {
835
+ toolInstallationId: identity.toolInstallationId,
836
+ source: identity.source,
837
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
838
+ sessionId: identity.sessionId ?? void 0,
839
+ workspaceHash: identity.workspaceHash ?? void 0,
840
+ consentScope: tool_contract_1.ASCENDA_CONSENT_SCOPE,
841
+ provenance: tool_contract_1.ASCENDA_PROVENANCE,
842
+ privacyMode: "metadata_only",
843
+ ...mapped,
844
+ metadata: mapped.metadata ?? {}
845
+ };
846
+ }
847
+ var DEFAULT_TIMEOUT_MS = 5e3;
848
+ var RETRY_DELAY_MS = 250;
606
849
  var AscendaEventSender2 = class {
607
850
  config;
608
851
  eventWriteToken;
852
+ lastState;
609
853
  constructor(config2) {
610
854
  this.config = config2;
611
855
  this.eventWriteToken = config2.eventWriteToken;
612
856
  }
613
857
  async send(mapped) {
614
- const payload = {
615
- toolInstallationId: this.config.toolInstallationId,
616
- source: this.config.source,
617
- occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
618
- sessionId: this.config.sessionId ?? void 0,
619
- workspaceHash: this.config.workspaceHash ?? void 0,
620
- consentScope: tool_contract_1.ASCENDA_CONSENT_SCOPE,
621
- provenance: tool_contract_1.ASCENDA_PROVENANCE,
622
- privacyMode: "metadata_only",
623
- ...mapped,
624
- metadata: mapped.metadata ?? {}
625
- };
626
- return this.post(payload);
858
+ return this.post(buildEventPayload(this.config, mapped));
627
859
  }
628
860
  /**
629
861
  * Sends one of the six agent-observed types (dark-flow-gap-analysis §2.1).
@@ -694,32 +926,263 @@ var require_eventSender = __commonJS({
694
926
  };
695
927
  return this.post(payload);
696
928
  }
929
+ /**
930
+ * The single choke point every event passes through, and therefore the only
931
+ * honest place to journal one. Recording here rather than in each adapter is
932
+ * deliberate: Claude Code, Codex, the GitHub collector and the MCP server all
933
+ * send through this method, and the defect being fixed showed up in three
934
+ * separate components because each was left to notice its own failures.
935
+ */
697
936
  async post(payload) {
698
- let result = await (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
699
- if (result === "auth_failed") {
700
- const renewed = await this.renewEventToken();
701
- if (!renewed)
702
- return "auth_failed";
703
- result = await (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
937
+ const outcome = await this.attempt(payload);
938
+ this.lastState = (0, stateStore_1.recordSendOutcome)(this.stateFilePath(), this.config.toolInstallationId, outcome.result, {
939
+ httpStatus: outcome.httpStatus,
940
+ errorCode: outcome.errorCode,
941
+ detail: outcome.detail
942
+ });
943
+ this.log(payload, outcome.result);
944
+ return outcome.result;
945
+ }
946
+ /**
947
+ * Two recoveries, each tried once. A rejected token gets a renewal and one
948
+ * replay — rotation is expected and should heal unattended. A transport
949
+ * error gets one retry, because the common cases (a restarting instance, a
950
+ * proxy blip, a 429) clear in well under a second and the alternative is
951
+ * losing the event outright.
952
+ */
953
+ async attempt(payload) {
954
+ const outcome = await (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
955
+ if (outcome.result === "auth_failed") {
956
+ if (!await this.renewEventToken())
957
+ return outcome;
958
+ return (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
704
959
  }
705
- return result;
960
+ if (outcome.result === "transport_error" && (outcome.httpStatus === void 0 || (0, http_1.isRetryableStatus)(outcome.httpStatus))) {
961
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
962
+ return (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
963
+ }
964
+ return outcome;
965
+ }
966
+ /**
967
+ * The state written by the most recent send, so a caller can decide whether
968
+ * to surface a one-time notice without re-reading the journal it just wrote.
969
+ */
970
+ get state() {
971
+ return this.lastState;
972
+ }
973
+ stateFilePath() {
974
+ return this.config.stateFilePath ?? (0, stateStore_1.defaultStateFilePath)(this.config.toolInstallationId);
975
+ }
976
+ /** Never throws: a renewal that errors is a failed renewal, not a failed turn. */
977
+ /**
978
+ * Every send path funnels through {@link post}, so semantic and
979
+ * collaboration signals are logged on the same terms as host events — the
980
+ * log would be misleading as an audit of what left the machine otherwise.
981
+ *
982
+ * An unreachable backend used to be logged as `other` from a catch block.
983
+ * It is now `transport_error` through the ordinary path, because the
984
+ * transport returns that outcome instead of throwing.
985
+ */
986
+ log(payload, delivery) {
987
+ const logFile = this.config.eventLogFile === void 0 ? (0, eventLog_1.resolveEventLogPath)() : this.config.eventLogFile;
988
+ if (!logFile)
989
+ return;
990
+ (0, eventLog_1.appendEventLog)(logFile, { loggedAt: (/* @__PURE__ */ new Date()).toISOString(), delivery, payload });
706
991
  }
707
992
  async renewEventToken() {
708
- const renewed = await (0, http_1.renewToolToken)(this.config.apiBaseUrl, this.eventWriteToken, this.signal());
709
- if (!renewed)
993
+ try {
994
+ const renewed = await (0, http_1.renewToolToken)(this.config.apiBaseUrl, this.eventWriteToken, this.signal());
995
+ if (!renewed)
996
+ return false;
997
+ this.eventWriteToken = renewed.eventWriteToken;
998
+ (0, tokenStore_1.persistEventWriteToken)(this.config.tokenFilePath, renewed.eventWriteToken);
999
+ return true;
1000
+ } catch {
710
1001
  return false;
711
- this.eventWriteToken = renewed.eventWriteToken;
712
- (0, tokenStore_1.persistEventWriteToken)(this.config.tokenFilePath, renewed.eventWriteToken);
713
- return true;
1002
+ }
714
1003
  }
715
1004
  signal() {
716
- return this.config.timeoutMs ? AbortSignal.timeout(this.config.timeoutMs) : void 0;
1005
+ return AbortSignal.timeout(this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
717
1006
  }
718
1007
  };
719
1008
  exports.AscendaEventSender = AscendaEventSender2;
720
1009
  }
721
1010
  });
722
1011
 
1012
+ // ../packages/tool-kit/out/hookAdapter.js
1013
+ var require_hookAdapter = __commonJS({
1014
+ "../packages/tool-kit/out/hookAdapter.js"(exports) {
1015
+ "use strict";
1016
+ Object.defineProperty(exports, "__esModule", { value: true });
1017
+ exports.DEFAULT_API_BASE_URL = void 0;
1018
+ exports.loadCliAgentConfig = loadCliAgentConfig;
1019
+ exports.deliverHookEvents = deliverHookEvents;
1020
+ var eventLog_1 = require_eventLog();
1021
+ var eventSender_1 = require_eventSender();
1022
+ var tokenStore_1 = require_tokenStore();
1023
+ exports.DEFAULT_API_BASE_URL = "https://api.ascenda.one";
1024
+ function loadCliAgentConfig(toolType, sessionIdFromHook) {
1025
+ const apiBaseUrl = (process.env.ASCENDA_API_BASE_URL ?? exports.DEFAULT_API_BASE_URL).replace(/\/$/, "");
1026
+ const toolInstallationIdRaw = process.env.ASCENDA_TOOL_INSTALLATION_ID;
1027
+ if (!toolInstallationIdRaw)
1028
+ throw new Error("Missing ASCENDA_TOOL_INSTALLATION_ID");
1029
+ const toolInstallationId = toolInstallationIdRaw.trim().includes(":") ? toolInstallationIdRaw.trim() : `${toolType}:${toolInstallationIdRaw.trim()}`;
1030
+ const tokenFilePath = process.env.ASCENDA_EVENT_WRITE_TOKEN_FILE ?? (0, tokenStore_1.defaultTokenFilePath)(toolInstallationId);
1031
+ const fileToken = (0, tokenStore_1.readTokenFile)(tokenFilePath);
1032
+ const eventWriteToken = fileToken ?? process.env.ASCENDA_EVENT_WRITE_TOKEN;
1033
+ if (!eventWriteToken)
1034
+ throw new Error("Missing ASCENDA_EVENT_WRITE_TOKEN (or token file)");
1035
+ if (!fileToken)
1036
+ (0, tokenStore_1.persistEventWriteToken)(tokenFilePath, eventWriteToken);
1037
+ return {
1038
+ apiBaseUrl,
1039
+ toolInstallationId,
1040
+ eventWriteToken,
1041
+ tokenFilePath,
1042
+ sessionId: process.env.ASCENDA_SESSION_ID ?? sessionIdFromHook ?? null,
1043
+ workspaceHash: process.env.ASCENDA_WORKSPACE_HASH ?? null,
1044
+ // Agents await command hooks; fail fast rather than stall the user's turn.
1045
+ timeoutMs: parsePositiveInt(process.env.ASCENDA_HTTP_TIMEOUT_MS) ?? 3e3
1046
+ };
1047
+ }
1048
+ async function deliverHookEvents(events, options) {
1049
+ if (events.length === 0)
1050
+ return;
1051
+ const notice = options.onNotice ?? ((message) => console.error(message));
1052
+ let config2;
1053
+ try {
1054
+ config2 = loadCliAgentConfig(options.toolType, options.sessionId);
1055
+ } catch (error2) {
1056
+ const logFile = (0, eventLog_1.resolveEventLogPath)();
1057
+ if (!logFile)
1058
+ throw error2;
1059
+ for (const event of events) {
1060
+ (0, eventLog_1.appendEventLog)(logFile, {
1061
+ loggedAt: (/* @__PURE__ */ new Date()).toISOString(),
1062
+ delivery: "not_sent",
1063
+ payload: (0, eventSender_1.buildEventPayload)({
1064
+ toolInstallationId: `${options.toolType}:unpaired`,
1065
+ source: options.source,
1066
+ sessionId: options.sessionId ?? null,
1067
+ workspaceHash: process.env.ASCENDA_WORKSPACE_HASH ?? null
1068
+ }, event)
1069
+ });
1070
+ }
1071
+ return;
1072
+ }
1073
+ const sender = new eventSender_1.AscendaEventSender({
1074
+ apiBaseUrl: config2.apiBaseUrl,
1075
+ toolInstallationId: config2.toolInstallationId,
1076
+ source: options.source,
1077
+ eventWriteToken: config2.eventWriteToken,
1078
+ tokenFilePath: config2.tokenFilePath,
1079
+ sessionId: config2.sessionId,
1080
+ workspaceHash: config2.workspaceHash,
1081
+ timeoutMs: config2.timeoutMs
1082
+ });
1083
+ for (const event of events) {
1084
+ const result = await sender.send(event);
1085
+ if (result === "accepted")
1086
+ continue;
1087
+ if (result === "consent_missing") {
1088
+ notice("Ascenda telemetry paused: renew IDE telemetry consent in the Ascenda app.");
1089
+ } else if (result === "auth_failed") {
1090
+ notice("Ascenda telemetry paused: connection revoked or expired. Re-pair via an Ascenda IDE extension or pairing-sim.");
1091
+ } else if (result === "transport_error") {
1092
+ notice("Ascenda telemetry paused: the ingest endpoint could not be reached. Your work is unaffected.");
1093
+ } else {
1094
+ notice(`Ascenda telemetry rejected: ${result}`);
1095
+ }
1096
+ return;
1097
+ }
1098
+ }
1099
+ function parsePositiveInt(value) {
1100
+ const n = Number(value);
1101
+ return Number.isInteger(n) && n > 0 ? n : void 0;
1102
+ }
1103
+ }
1104
+ });
1105
+
1106
+ // ../packages/tool-kit/out/turnState.js
1107
+ var require_turnState = __commonJS({
1108
+ "../packages/tool-kit/out/turnState.js"(exports) {
1109
+ "use strict";
1110
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
1111
+ if (k2 === void 0) k2 = k;
1112
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1113
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1114
+ desc = { enumerable: true, get: function() {
1115
+ return m[k];
1116
+ } };
1117
+ }
1118
+ Object.defineProperty(o, k2, desc);
1119
+ } : function(o, m, k, k2) {
1120
+ if (k2 === void 0) k2 = k;
1121
+ o[k2] = m[k];
1122
+ });
1123
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
1124
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1125
+ } : function(o, v) {
1126
+ o["default"] = v;
1127
+ });
1128
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
1129
+ var ownKeys = function(o) {
1130
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
1131
+ var ar = [];
1132
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
1133
+ return ar;
1134
+ };
1135
+ return ownKeys(o);
1136
+ };
1137
+ return function(mod) {
1138
+ if (mod && mod.__esModule) return mod;
1139
+ var result = {};
1140
+ if (mod != null) {
1141
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1142
+ }
1143
+ __setModuleDefault(result, mod);
1144
+ return result;
1145
+ };
1146
+ }();
1147
+ Object.defineProperty(exports, "__esModule", { value: true });
1148
+ exports.recordTurnStart = recordTurnStart;
1149
+ exports.consumeTurnDurationMs = consumeTurnDurationMs;
1150
+ var fs = __importStar(__require("fs"));
1151
+ var os = __importStar(__require("os"));
1152
+ var path = __importStar(__require("path"));
1153
+ var stateDir = () => process.env.ASCENDA_STATE_DIR ?? path.join(os.homedir(), ".ascenda", "state");
1154
+ function turnFile(agent, sessionId) {
1155
+ return path.join(stateDir(), `${sanitize(agent)}-turn-${sanitize(sessionId)}`);
1156
+ }
1157
+ function sanitize(value) {
1158
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
1159
+ }
1160
+ function recordTurnStart(agent, sessionId, now = Date.now()) {
1161
+ if (!sessionId)
1162
+ return;
1163
+ try {
1164
+ fs.mkdirSync(stateDir(), { recursive: true });
1165
+ fs.writeFileSync(turnFile(agent, sessionId), String(now), { encoding: "utf8", mode: 384 });
1166
+ } catch {
1167
+ }
1168
+ }
1169
+ function consumeTurnDurationMs(agent, sessionId, now = Date.now()) {
1170
+ if (!sessionId)
1171
+ return void 0;
1172
+ const file = turnFile(agent, sessionId);
1173
+ try {
1174
+ const started = Number(fs.readFileSync(file, "utf8").trim());
1175
+ fs.rmSync(file, { force: true });
1176
+ if (!Number.isFinite(started) || started <= 0 || started > now)
1177
+ return void 0;
1178
+ return now - started;
1179
+ } catch {
1180
+ return void 0;
1181
+ }
1182
+ }
1183
+ }
1184
+ });
1185
+
723
1186
  // ../packages/tool-kit/out/salt.js
724
1187
  var require_salt = __commonJS({
725
1188
  "../packages/tool-kit/out/salt.js"(exports) {
@@ -934,7 +1397,8 @@ var require_out2 = __commonJS({
934
1397
  "../packages/tool-kit/out/index.js"(exports) {
935
1398
  "use strict";
936
1399
  Object.defineProperty(exports, "__esModule", { value: true });
937
- exports.parseIngestResponse = exports.postToolEventsBatch = exports.postToolEvent = exports.renewToolToken = exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = exports.hashWithMachineSalt = exports.readOrCreateMachineSalt = exports.machineSaltFilePath = exports.readTokenFile = exports.persistEventWriteToken = exports.defaultTokenFilePath = exports.AscendaSemanticEventError = exports.AscendaEventSender = exports.looksLikeCorrection = exports.outcomeForHook = exports.inferOutcome = exports.getNestedNumber = exports.getNestedString = exports.getNested = exports.getNumber = exports.getString = exports.isAfterHours = exports.bucketDurationMs = exports.bucketLinesChanged = exports.invitesDebrief = exports.classifyWorkMilestone = exports.isReworkGitAction = exports.classifyGitAction = exports.isVerificationCommand = exports.classifyCommand = void 0;
1400
+ exports.postToolEventsBatch = exports.postToolEvent = exports.renewToolToken = exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = exports.hashWithMachineSalt = exports.readOrCreateMachineSalt = exports.machineSaltFilePath = exports.markFailureNotified = exports.shouldAnnounceFailure = exports.recordSendOutcome = exports.readCollectorState = exports.defaultStateFilePath = exports.readTokenFile = exports.persistEventWriteToken = exports.defaultTokenFilePath = exports.recordTurnStart = exports.consumeTurnDurationMs = exports.loadCliAgentConfig = exports.deliverHookEvents = exports.DEFAULT_API_BASE_URL = exports.resolveEventLogPath = exports.expandUserPath = exports.appendEventLog = exports.EVENT_LOG_ENV_VAR = exports.buildEventPayload = exports.AscendaSemanticEventError = exports.AscendaEventSender = exports.looksLikeCorrection = exports.outcomeForHook = exports.inferOutcome = exports.getNestedNumber = exports.getNestedString = exports.getNested = exports.getNumber = exports.getString = exports.isAfterHours = exports.bucketDurationMs = exports.bucketLinesChanged = exports.invitesDebrief = exports.classifyWorkMilestone = exports.isReworkGitAction = exports.classifyGitAction = exports.isVerificationCommand = exports.classifyCommand = void 0;
1401
+ exports.isRetryableStatus = exports.parseIngestResponse = void 0;
938
1402
  var commandClassifier_1 = require_commandClassifier();
939
1403
  Object.defineProperty(exports, "classifyCommand", { enumerable: true, get: function() {
940
1404
  return commandClassifier_1.classifyCommand;
@@ -999,6 +1463,39 @@ var require_out2 = __commonJS({
999
1463
  Object.defineProperty(exports, "AscendaSemanticEventError", { enumerable: true, get: function() {
1000
1464
  return eventSender_1.AscendaSemanticEventError;
1001
1465
  } });
1466
+ Object.defineProperty(exports, "buildEventPayload", { enumerable: true, get: function() {
1467
+ return eventSender_1.buildEventPayload;
1468
+ } });
1469
+ var eventLog_1 = require_eventLog();
1470
+ Object.defineProperty(exports, "EVENT_LOG_ENV_VAR", { enumerable: true, get: function() {
1471
+ return eventLog_1.EVENT_LOG_ENV_VAR;
1472
+ } });
1473
+ Object.defineProperty(exports, "appendEventLog", { enumerable: true, get: function() {
1474
+ return eventLog_1.appendEventLog;
1475
+ } });
1476
+ Object.defineProperty(exports, "expandUserPath", { enumerable: true, get: function() {
1477
+ return eventLog_1.expandUserPath;
1478
+ } });
1479
+ Object.defineProperty(exports, "resolveEventLogPath", { enumerable: true, get: function() {
1480
+ return eventLog_1.resolveEventLogPath;
1481
+ } });
1482
+ var hookAdapter_1 = require_hookAdapter();
1483
+ Object.defineProperty(exports, "DEFAULT_API_BASE_URL", { enumerable: true, get: function() {
1484
+ return hookAdapter_1.DEFAULT_API_BASE_URL;
1485
+ } });
1486
+ Object.defineProperty(exports, "deliverHookEvents", { enumerable: true, get: function() {
1487
+ return hookAdapter_1.deliverHookEvents;
1488
+ } });
1489
+ Object.defineProperty(exports, "loadCliAgentConfig", { enumerable: true, get: function() {
1490
+ return hookAdapter_1.loadCliAgentConfig;
1491
+ } });
1492
+ var turnState_1 = require_turnState();
1493
+ Object.defineProperty(exports, "consumeTurnDurationMs", { enumerable: true, get: function() {
1494
+ return turnState_1.consumeTurnDurationMs;
1495
+ } });
1496
+ Object.defineProperty(exports, "recordTurnStart", { enumerable: true, get: function() {
1497
+ return turnState_1.recordTurnStart;
1498
+ } });
1002
1499
  var tokenStore_1 = require_tokenStore();
1003
1500
  Object.defineProperty(exports, "defaultTokenFilePath", { enumerable: true, get: function() {
1004
1501
  return tokenStore_1.defaultTokenFilePath;
@@ -1009,6 +1506,22 @@ var require_out2 = __commonJS({
1009
1506
  Object.defineProperty(exports, "readTokenFile", { enumerable: true, get: function() {
1010
1507
  return tokenStore_1.readTokenFile;
1011
1508
  } });
1509
+ var stateStore_1 = require_stateStore();
1510
+ Object.defineProperty(exports, "defaultStateFilePath", { enumerable: true, get: function() {
1511
+ return stateStore_1.defaultStateFilePath;
1512
+ } });
1513
+ Object.defineProperty(exports, "readCollectorState", { enumerable: true, get: function() {
1514
+ return stateStore_1.readCollectorState;
1515
+ } });
1516
+ Object.defineProperty(exports, "recordSendOutcome", { enumerable: true, get: function() {
1517
+ return stateStore_1.recordSendOutcome;
1518
+ } });
1519
+ Object.defineProperty(exports, "shouldAnnounceFailure", { enumerable: true, get: function() {
1520
+ return stateStore_1.shouldAnnounceFailure;
1521
+ } });
1522
+ Object.defineProperty(exports, "markFailureNotified", { enumerable: true, get: function() {
1523
+ return stateStore_1.markFailureNotified;
1524
+ } });
1012
1525
  var salt_1 = require_salt();
1013
1526
  Object.defineProperty(exports, "machineSaltFilePath", { enumerable: true, get: function() {
1014
1527
  return salt_1.machineSaltFilePath;
@@ -1054,6 +1567,9 @@ var require_out2 = __commonJS({
1054
1567
  Object.defineProperty(exports, "parseIngestResponse", { enumerable: true, get: function() {
1055
1568
  return http_1.parseIngestResponse;
1056
1569
  } });
1570
+ Object.defineProperty(exports, "isRetryableStatus", { enumerable: true, get: function() {
1571
+ return http_1.isRetryableStatus;
1572
+ } });
1057
1573
  }
1058
1574
  });
1059
1575
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "publishConfig": { "access": "public" },
3
3
  "name": "@ascenda-one/agent-mcp",
4
- "version": "0.1.8",
4
+ "version": "0.1.9",
5
5
  "description": "MCP server exposing ascenda_emit_work_signal — the one submission interface for agent-observed (semantic) work-friction signals.",
6
6
  "repository": {
7
7
  "type": "git",