@ascenda-one/agent-mcp 0.1.14 → 0.1.16

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 +1823 -111
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -204,6 +204,102 @@ var require_workMilestoneClassifier = __commonJS({
204
204
  }
205
205
  });
206
206
 
207
+ // ../packages/tool-kit/out/autonomyBand.js
208
+ var require_autonomyBand = __commonJS({
209
+ "../packages/tool-kit/out/autonomyBand.js"(exports) {
210
+ "use strict";
211
+ Object.defineProperty(exports, "__esModule", { value: true });
212
+ exports.autonomyBand = autonomyBand;
213
+ function autonomyBand(mode) {
214
+ if (typeof mode !== "string")
215
+ return "unknown";
216
+ return BAND_BY_MODE[mode] ?? "unknown";
217
+ }
218
+ var BAND_BY_MODE = {
219
+ plan: "planning",
220
+ default: "supervised",
221
+ accept_edits: "edits_auto",
222
+ // Two tokens, one band — and the reason the tokens stayed two. They differ
223
+ // in how the user arrived at the posture rather than in how much the agent
224
+ // may then do unasked, so today they read the same. If that ever stops being
225
+ // true, this line changes and the whole corpus re-reads correctly, because
226
+ // the wire never collapsed them.
227
+ auto: "delegated",
228
+ dont_ask: "delegated",
229
+ bypass_permissions: "unsupervised"
230
+ };
231
+ }
232
+ });
233
+
234
+ // ../packages/tool-kit/out/modelClassifier.js
235
+ var require_modelClassifier = __commonJS({
236
+ "../packages/tool-kit/out/modelClassifier.js"(exports) {
237
+ "use strict";
238
+ Object.defineProperty(exports, "__esModule", { value: true });
239
+ exports.classifyModelClass = classifyModelClass;
240
+ function classifyModelClass(raw) {
241
+ const candidate = raw;
242
+ if (candidate === void 0 || candidate === null)
243
+ return void 0;
244
+ if (typeof candidate !== "string")
245
+ return "unknown";
246
+ const value = candidate.trim().toLowerCase();
247
+ if (!value)
248
+ return void 0;
249
+ if (ROUTER_SENTINEL.test(value))
250
+ return "router:auto";
251
+ const vendor = readModelVendor(value);
252
+ if (vendor === void 0)
253
+ return "unknown";
254
+ for (const [pattern, modelClass] of TIER_PATTERNS_BY_VENDOR[vendor]) {
255
+ if (pattern.test(value))
256
+ return modelClass;
257
+ }
258
+ return UNKNOWN_TIER_BY_VENDOR[vendor];
259
+ }
260
+ function readModelVendor(value) {
261
+ for (const [pattern, vendor] of VENDOR_PATTERNS) {
262
+ if (pattern.test(value))
263
+ return vendor;
264
+ }
265
+ return void 0;
266
+ }
267
+ var ROUTER_SENTINEL = /^(?:[a-z0-9][a-z0-9._-]*\/)?(?:auto|default)$/;
268
+ var VENDOR_PATTERNS = [
269
+ [/\b(anthropic|claude|opus|sonnet|haiku|fable)\b/, "anthropic"],
270
+ [/\b(openai|gpt|o[1-9])\b/, "openai"],
271
+ [/\b(google|gemini|vertex)\b/, "google"],
272
+ // xAI carries no corporate prefix in any observed id — the family name is
273
+ // the whole marker, exactly as `claude` and `gemini` are for theirs.
274
+ [/\b(xai|grok)\b/, "xai"],
275
+ [/\b(ollama|llamacpp|on[-_]?device|local)\b/, "local"]
276
+ ];
277
+ var TIER_PATTERNS_BY_VENDOR = {
278
+ anthropic: [
279
+ [/\bopus\b/, "anthropic:opus"],
280
+ [/\bsonnet\b/, "anthropic:sonnet"],
281
+ [/\bhaiku\b/, "anthropic:haiku"],
282
+ [/\bfable\b/, "anthropic:fable"]
283
+ ],
284
+ openai: [[/\bgpt\b/, "openai:gpt"]],
285
+ google: [[/\bgemini\b/, "google:gemini"]],
286
+ // One tier for now. The line's coding variants (`grok-code-fast-1`) are the
287
+ // same tier word plus a suffix, and splitting them off would be inventing a
288
+ // distinction the ids do not yet draw — `<vendor>:unknown` is waiting for
289
+ // the day one does.
290
+ xai: [[/\bgrok\b/, "xai:grok"]],
291
+ local: [[/\b(ollama|llamacpp|on[-_]?device)\b/, "local:on_device"]]
292
+ };
293
+ var UNKNOWN_TIER_BY_VENDOR = {
294
+ anthropic: "anthropic:unknown",
295
+ openai: "openai:unknown",
296
+ google: "google:unknown",
297
+ xai: "xai:unknown",
298
+ local: "local:unknown"
299
+ };
300
+ }
301
+ });
302
+
207
303
  // ../packages/tool-kit/out/buckets.js
208
304
  var require_buckets = __commonJS({
209
305
  "../packages/tool-kit/out/buckets.js"(exports) {
@@ -298,11 +394,298 @@ var require_afterHours = __commonJS({
298
394
  }
299
395
  });
300
396
 
397
+ // ../packages/tool-contract/out/metricKeys.js
398
+ var require_metricKeys = __commonJS({
399
+ "../packages/tool-contract/out/metricKeys.js"(exports) {
400
+ "use strict";
401
+ Object.defineProperty(exports, "__esModule", { value: true });
402
+ exports.METRIC_KEYS = void 0;
403
+ exports.backendMetricKeys = backendMetricKeys;
404
+ var CONTEXT_WINDOW_CANONICAL_ALIASES = [
405
+ "contextWindowPeakPct",
406
+ "context_window_peak_pct",
407
+ "contextWindowPct",
408
+ "context_window_pct"
409
+ ];
410
+ var CONTEXT_WINDOW_CURSOR_PERCENT_ALIASES = ["contextUsagePercent"];
411
+ exports.METRIC_KEYS = {
412
+ // ── Read by a backend reader ────────────────────────────────────────────
413
+ contextWindowPeakPct: {
414
+ readBy: ["backend", "handoff"],
415
+ backendAliases: CONTEXT_WINDOW_CANONICAL_ALIASES,
416
+ unit: "fraction of the context window (0\u20131; uncapped for >200k contexts)",
417
+ note: "Claude Code reports a true per-session peak. Cursor reports its composer's last known occupancy under the same key \u2014 the closest its store can answer, and not the same measurement."
418
+ },
419
+ contextUsagePercent: {
420
+ readBy: ["backend", "handoff"],
421
+ backendAliases: CONTEXT_WINDOW_CURSOR_PERCENT_ALIASES,
422
+ unit: "percent (0\u2013100)",
423
+ note: "Cursor's own column name. Superseded by contextWindowPeakPct on the wire; kept because the handoff reads it and imported rows carry it. Unit-explicit on the backend: always divided by 100, never put through the fraction-or-percent heuristic."
424
+ },
425
+ promptCount: { readBy: ["backend", "handoff"], backendAliases: ["promptCount", "prompt_count"] },
426
+ sessionMinutes: { readBy: ["backend"], backendAliases: ["sessionMinutes", "session_minutes"], unit: "minutes" },
427
+ durationBucket: { readBy: ["backend", "handoff"], backendAliases: ["durationBucket", "duration_bucket"] },
428
+ afterHoursPrompts: { readBy: ["backend", "handoff"], backendAliases: ["afterHoursPrompts", "after_hours_prompts"] },
429
+ inputTokens: { readBy: ["backend"], backendAliases: ["inputTokens", "input_tokens"], unit: "tokens" },
430
+ outputTokens: { readBy: ["backend"], backendAliases: ["outputTokens", "output_tokens"], unit: "tokens" },
431
+ cacheReadTokens: { readBy: ["backend"], backendAliases: ["cacheReadTokens", "cache_read_tokens"], unit: "tokens" },
432
+ queuedPrompts: { readBy: ["backend"], backendAliases: ["queuedPrompts", "queued_prompts"] },
433
+ linesChangedBucket: { readBy: ["backend"], backendAliases: ["linesChangedBucket", "lines_changed_bucket"] },
434
+ // ── Read by the local handoff only ──────────────────────────────────────
435
+ activeMinutes: { readBy: ["handoff"], unit: "minutes" },
436
+ /**
437
+ * The two halves of `activeMinutes`, and deliberately two keys.
438
+ *
439
+ * They partition it exactly, so a reader can add them — but there is no
440
+ * third key holding the sum, because the sum is `activeMinutes` and it
441
+ * already exists. Presenting one combined "active" figure in place of these
442
+ * is the thing the split was added to stop: an hour of typing and an hour of
443
+ * watching an agent work are not the same hour, and a single number says
444
+ * they are.
445
+ */
446
+ handsOnMinutes: {
447
+ readBy: ["handoff"],
448
+ unit: "minutes",
449
+ note: "Active time immediately preceding a human prompt \u2014 the only interval a transcript can show a person present for, because the prompt at its end is the evidence."
450
+ },
451
+ agentSupervisingMinutes: {
452
+ readBy: ["handoff"],
453
+ unit: "minutes",
454
+ note: "The remaining active time: the agent was working and the person was not typing. NOT a claim that anyone watched it \u2014 nothing in a transcript could show that. Never render as attention."
455
+ },
456
+ // The split's honesty counters. Read by neither the backend nor the handoff
457
+ // on purpose: they exist so a thin or posture-blind session can be told from
458
+ // a complete one, and a reader that ignores them is choosing to, rather than
459
+ // being unable to.
460
+ activeSplitInstants: {
461
+ readBy: ["diagnostic"],
462
+ note: "Distinct timestamps the split ran over, after collapsing ties. The denominator: two minutes off four instants and off four hundred are not the same measurement."
463
+ },
464
+ activeSplitUndatedLines: {
465
+ readBy: ["diagnostic"],
466
+ note: "Known lines carrying a timestamp that would not parse. Absent from the timeline, so both halves are short by an unknown amount and only this says so."
467
+ },
468
+ activeSplitUnposturedInstants: {
469
+ readBy: ["diagnostic"],
470
+ note: "Instants reached before any permissionMode had been declared. Their supervising time lands in the unknown band, which is a blind spot rather than a posture."
471
+ },
472
+ afterHoursRequests: { readBy: ["handoff"] },
473
+ approximateLintErrorsCount: { readBy: ["handoff"] },
474
+ canceledCount: { readBy: ["handoff"] },
475
+ chatEditCount: { readBy: ["handoff"] },
476
+ compactionCount: {
477
+ readBy: ["handoff"],
478
+ note: "The backend counts context_compression_* event rows, not this key. Both are emitted; this one is for the handoff."
479
+ },
480
+ contextWindowPeakTokens: {
481
+ readBy: ["handoff"],
482
+ unit: "tokens",
483
+ note: "The measured quantity, with no assumed denominator. Prefer this to the ratio for any within-person baseline."
484
+ },
485
+ date: { readBy: ["handoff"] },
486
+ errorCount: { readBy: ["handoff"] },
487
+ filesChangedCount: { readBy: ["handoff"] },
488
+ humanChangesCount: { readBy: ["handoff"] },
489
+ linesAdded: { readBy: ["handoff"] },
490
+ linesRemoved: { readBy: ["handoff"] },
491
+ primaryModel: { readBy: ["handoff"] },
492
+ requestCount: { readBy: ["handoff"] },
493
+ sessionStartedAt: { readBy: ["handoff"] },
494
+ subagentComposers: { readBy: ["handoff"] },
495
+ subagentToolCallCount: {
496
+ readBy: ["handoff"],
497
+ note: "Claude Code only: calls made inside subagent transcripts, kept out of toolCallCount so the main-loop count matches what the wire events carry."
498
+ },
499
+ subagentTranscripts: { readBy: ["handoff"] },
500
+ toolCallCount: {
501
+ readBy: ["handoff"],
502
+ note: "The backend counts ai_tool_call_started event rows, not this key \u2014 same split as compactionCount. Deduplicated on the tool-call id; see each extractor for what one call means in its store."
503
+ },
504
+ toolCallsUndated: {
505
+ readBy: ["handoff"],
506
+ note: "Cursor only: calls whose every record carries an empty createdAt. Counted in toolCallCount, but no event exists for them \u2014 the wire total is smaller than this session count by exactly this number."
507
+ },
508
+ toolFailureCount: { readBy: ["handoff"] },
509
+ totalEntryCount: { readBy: ["handoff"] },
510
+ userModifiedEditCount: {
511
+ readBy: ["handoff"],
512
+ note: "Null, never 0 \u2014 Claude Code never sets userModified true, so 0 would assert 'no AI edit was ever corrected by hand'."
513
+ },
514
+ // ── Diagnostic: read by nobody, on purpose ──────────────────────────────
515
+ abandonedPromptCount: { readBy: ["diagnostic"] },
516
+ // Epoch-marker metrics. The marker is local-only and never reaches the wire
517
+ // (see EXTRACTION_EPOCH_KIND), but it travels as a NormalizedHistoricalEvent
518
+ // and so is keyed by the same vocabulary.
519
+ windowOldest: { readBy: ["handoff"], note: "Oldest event the extraction saw \u2014 the marker's left edge." },
520
+ windowNewest: { readBy: ["handoff"], note: "Newest event the extraction saw \u2014 the marker's right edge." },
521
+ projectsWithNoReadableTranscript: { readBy: ["diagnostic"] },
522
+ unparsedComposerHeaders: { readBy: ["diagnostic"] },
523
+ unknownComposerHeaderTypes: { readBy: ["diagnostic"] },
524
+ orphanedBubbles: { readBy: ["diagnostic"] },
525
+ orphanedSubagentBubbles: { readBy: ["diagnostic"] },
526
+ sessionsWithoutTimeline: { readBy: ["diagnostic"] },
527
+ emptyComposers: { readBy: ["diagnostic"] },
528
+ apiErrorCount: { readBy: ["diagnostic"] },
529
+ assistantTurns: { readBy: ["diagnostic"] },
530
+ compactionAutoCount: { readBy: ["diagnostic"] },
531
+ compactionManualCount: { readBy: ["diagnostic"] },
532
+ editDayCount: { readBy: ["diagnostic"] },
533
+ emptyChatSessions: { readBy: ["diagnostic"] },
534
+ gitBranch: { readBy: ["diagnostic"] },
535
+ linesChanged: { readBy: ["diagnostic"] },
536
+ malformedChatSessionLines: { readBy: ["diagnostic"] },
537
+ malformedHistoryEntries: { readBy: ["diagnostic"] },
538
+ mode: { readBy: ["diagnostic"] },
539
+ modelCount: { readBy: ["diagnostic"] },
540
+ modelSwitchCount: { readBy: ["diagnostic"] },
541
+ rapidRepromptCount: { readBy: ["diagnostic"] },
542
+ schemaUnreadable: { readBy: ["diagnostic"] },
543
+ sessionCount: { readBy: ["diagnostic"] },
544
+ sessionsFromBubbleTimeline: { readBy: ["diagnostic"] },
545
+ sessionsFromCheckpointTimeline: { readBy: ["diagnostic"] },
546
+ sessionsFromRecencyTimeline: { readBy: ["diagnostic"] },
547
+ subagentAssistantTurns: { readBy: ["diagnostic"] },
548
+ subagentPrompts: { readBy: ["diagnostic"] },
549
+ subagentTokensTotal: { readBy: ["diagnostic"] },
550
+ toolName: {
551
+ readBy: ["diagnostic"],
552
+ note: "Set per ai_tool_call_started event by #43's extractors and shipped in wire metadata, but no reader resolves it server-side yet (the backend's ToolName column is MCP audit, not telemetry). Registered after the fact: #38 and #43 merged past each other, and the union caught it on the next compile \u2014 metaLines all over again."
553
+ },
554
+ toolResultCount: { readBy: ["diagnostic"] },
555
+ toolResultErrorCount: { readBy: ["diagnostic"] },
556
+ unknownBubbles: { readBy: ["diagnostic"] },
557
+ unknownLines: { readBy: ["diagnostic"] },
558
+ metaLines: {
559
+ readBy: ["diagnostic"],
560
+ note: 'Recognised-but-skipped transcript machinery (file-history-snapshot, queued-command, \u2026). Split out of unknownLines by #43 so that number keeps meaning "a type nobody has looked at". Registered here after the fact: #41 and #43 merged past each other, and the union caught it on the next compile \u2014 which is this module doing its job.'
561
+ },
562
+ unparsedBubbles: { readBy: ["diagnostic"] },
563
+ unparsedChatSessionFiles: { readBy: ["diagnostic"] },
564
+ unparsedHistoryFiles: { readBy: ["diagnostic"] },
565
+ unparsedLines: { readBy: ["diagnostic"] },
566
+ unreadableChatSessionFiles: { readBy: ["diagnostic"] },
567
+ unreadableHistoryFiles: { readBy: ["diagnostic"] },
568
+ unrecognisedChatSessionFiles: { readBy: ["diagnostic"] }
569
+ };
570
+ function backendMetricKeys() {
571
+ return Object.entries(exports.METRIC_KEYS).filter(([, spec]) => spec.readBy.includes("backend")).map(([key, spec]) => [key, spec.backendAliases ?? [key]]);
572
+ }
573
+ }
574
+ });
575
+
576
+ // ../packages/tool-contract/out/index.js
577
+ var require_out = __commonJS({
578
+ "../packages/tool-contract/out/index.js"(exports) {
579
+ "use strict";
580
+ Object.defineProperty(exports, "__esModule", { value: true });
581
+ exports.backendMetricKeys = exports.METRIC_KEYS = exports.ASCENDA_SEMANTIC_PROVENANCE = exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = exports.ASCENDA_PROVENANCE = exports.ASCENDA_CONSENT_SCOPE = exports.EVENT_WORKLOAD_CATEGORY = exports.TOOL_EVENT_DELIVERED_STATUSES = exports.IDEMPOTENCY_KEY_MAX_LENGTH = exports.EVENT_METADATA_FIELDS = exports.COLLABORATION_EVENT_TYPES = exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = void 0;
582
+ exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = [
583
+ "approach_churn_detected",
584
+ "goal_drift_detected",
585
+ "progress_stalled",
586
+ "progress_recovered",
587
+ "session_intention_declared",
588
+ "scope_change_declared"
589
+ ];
590
+ exports.COLLABORATION_EVENT_TYPES = [
591
+ "review_requested_of_me",
592
+ "review_given",
593
+ "pull_request_opened"
594
+ ];
595
+ exports.EVENT_METADATA_FIELDS = [
596
+ "language",
597
+ "fileType",
598
+ "durationBucket",
599
+ "tokenPressureBucket",
600
+ "linesChangedBucket",
601
+ "commandClass",
602
+ "gitAction",
603
+ "milestoneKind",
604
+ "branchHash",
605
+ "autonomyMode",
606
+ "modelClass",
607
+ "modelId",
608
+ "userModified",
609
+ "outcome",
610
+ "trigger",
611
+ "promptClass",
612
+ "reason",
613
+ "afterHours",
614
+ "activity",
615
+ "message",
616
+ "host",
617
+ "toolName",
618
+ "simulated",
619
+ "relatedEventType",
620
+ "skillVersion",
621
+ "taskFingerprint",
622
+ "importKey",
623
+ "extractionId",
624
+ "importSchema"
625
+ ];
626
+ exports.IDEMPOTENCY_KEY_MAX_LENGTH = 128;
627
+ exports.TOOL_EVENT_DELIVERED_STATUSES = ["accepted", "duplicate"];
628
+ exports.EVENT_WORKLOAD_CATEGORY = {
629
+ create_focus_session: "creation",
630
+ ai_prompt_submitted: "creation",
631
+ ai_generation_completed: "creation",
632
+ ai_file_write: "creation",
633
+ ai_file_edit: "creation",
634
+ editor_verification_activity: "verification",
635
+ compile_diagnostic: "verification",
636
+ editor_correction_activity: "supervision",
637
+ ai_correction_prompt: "supervision",
638
+ supervis_meeting_load: "supervision",
639
+ ai_tool_call_started: "supervision",
640
+ ai_tool_call_completed: "supervision",
641
+ ai_tool_call_failed: "supervision",
642
+ // Collaboration (the report's §4.2 collaboration family). Both review
643
+ // events are supervision: being asked to check work, and checking it, are
644
+ // the load the report's "verification overload" concern is about — the one
645
+ // that concentrates on senior engineers as a team adopts AI. Opening a pull
646
+ // request is creation: it is the point your own work leaves your hands.
647
+ review_requested_of_me: "supervision",
648
+ review_given: "supervision",
649
+ pull_request_opened: "creation",
650
+ context_pressure_high: "risk",
651
+ agent_loop_long: "risk",
652
+ after_hours_ai_session: "risk",
653
+ compile_error: "risk",
654
+ tool_failure: "risk",
655
+ recovery_offline_period: "neutral",
656
+ context_compression_manual: "neutral",
657
+ context_compression_auto: "neutral",
658
+ editor_activity: "neutral",
659
+ // Semantic (agent-observed) — see SEMANTIC_WORK_SIGNAL_EVENT_TYPES.
660
+ approach_churn_detected: "risk",
661
+ goal_drift_detected: "risk",
662
+ progress_stalled: "risk",
663
+ progress_recovered: "neutral",
664
+ session_intention_declared: "neutral",
665
+ scope_change_declared: "neutral"
666
+ };
667
+ exports.ASCENDA_CONSENT_SCOPE = "ide_telemetry";
668
+ exports.ASCENDA_PROVENANCE = "ai_work_telemetry";
669
+ exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = "semantic_work_signals";
670
+ exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = "workflow_telemetry";
671
+ exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = "historical_import";
672
+ exports.ASCENDA_SEMANTIC_PROVENANCE = "semantic_work_signals";
673
+ var metricKeys_1 = require_metricKeys();
674
+ Object.defineProperty(exports, "METRIC_KEYS", { enumerable: true, get: function() {
675
+ return metricKeys_1.METRIC_KEYS;
676
+ } });
677
+ Object.defineProperty(exports, "backendMetricKeys", { enumerable: true, get: function() {
678
+ return metricKeys_1.backendMetricKeys;
679
+ } });
680
+ }
681
+ });
682
+
301
683
  // ../packages/tool-kit/out/payload.js
302
684
  var require_payload = __commonJS({
303
685
  "../packages/tool-kit/out/payload.js"(exports) {
304
686
  "use strict";
305
687
  Object.defineProperty(exports, "__esModule", { value: true });
688
+ exports.mintIdempotencyKey = mintIdempotencyKey;
306
689
  exports.getString = getString;
307
690
  exports.getNumber = getNumber;
308
691
  exports.getNested = getNested;
@@ -311,6 +694,14 @@ var require_payload = __commonJS({
311
694
  exports.inferOutcome = inferOutcome;
312
695
  exports.outcomeForHook = outcomeForHook;
313
696
  exports.looksLikeCorrection = looksLikeCorrection;
697
+ var node_crypto_1 = __require("node:crypto");
698
+ var tool_contract_1 = require_out();
699
+ function mintIdempotencyKey() {
700
+ const key = (0, node_crypto_1.randomUUID)();
701
+ if (key.length > tool_contract_1.IDEMPOTENCY_KEY_MAX_LENGTH)
702
+ throw new Error("idempotency key exceeds the wire limit");
703
+ return key;
704
+ }
314
705
  function getString(input, keys) {
315
706
  for (const key of keys) {
316
707
  const value = input[key];
@@ -381,73 +772,6 @@ var require_payload = __commonJS({
381
772
  }
382
773
  });
383
774
 
384
- // ../packages/tool-contract/out/index.js
385
- var require_out = __commonJS({
386
- "../packages/tool-contract/out/index.js"(exports) {
387
- "use strict";
388
- Object.defineProperty(exports, "__esModule", { value: true });
389
- exports.ASCENDA_SEMANTIC_PROVENANCE = exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = exports.ASCENDA_PROVENANCE = exports.ASCENDA_CONSENT_SCOPE = exports.EVENT_WORKLOAD_CATEGORY = exports.COLLABORATION_EVENT_TYPES = exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = void 0;
390
- exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = [
391
- "approach_churn_detected",
392
- "goal_drift_detected",
393
- "progress_stalled",
394
- "progress_recovered",
395
- "session_intention_declared",
396
- "scope_change_declared"
397
- ];
398
- exports.COLLABORATION_EVENT_TYPES = [
399
- "review_requested_of_me",
400
- "review_given",
401
- "pull_request_opened"
402
- ];
403
- exports.EVENT_WORKLOAD_CATEGORY = {
404
- create_focus_session: "creation",
405
- ai_prompt_submitted: "creation",
406
- ai_generation_completed: "creation",
407
- ai_file_write: "creation",
408
- ai_file_edit: "creation",
409
- editor_verification_activity: "verification",
410
- compile_diagnostic: "verification",
411
- editor_correction_activity: "supervision",
412
- ai_correction_prompt: "supervision",
413
- supervis_meeting_load: "supervision",
414
- ai_tool_call_started: "supervision",
415
- ai_tool_call_completed: "supervision",
416
- ai_tool_call_failed: "supervision",
417
- // Collaboration (the report's §4.2 collaboration family). Both review
418
- // events are supervision: being asked to check work, and checking it, are
419
- // the load the report's "verification overload" concern is about — the one
420
- // that concentrates on senior engineers as a team adopts AI. Opening a pull
421
- // request is creation: it is the point your own work leaves your hands.
422
- review_requested_of_me: "supervision",
423
- review_given: "supervision",
424
- pull_request_opened: "creation",
425
- context_pressure_high: "risk",
426
- agent_loop_long: "risk",
427
- after_hours_ai_session: "risk",
428
- compile_error: "risk",
429
- tool_failure: "risk",
430
- recovery_offline_period: "neutral",
431
- context_compression_manual: "neutral",
432
- context_compression_auto: "neutral",
433
- editor_activity: "neutral",
434
- // Semantic (agent-observed) — see SEMANTIC_WORK_SIGNAL_EVENT_TYPES.
435
- approach_churn_detected: "risk",
436
- goal_drift_detected: "risk",
437
- progress_stalled: "risk",
438
- progress_recovered: "neutral",
439
- session_intention_declared: "neutral",
440
- scope_change_declared: "neutral"
441
- };
442
- exports.ASCENDA_CONSENT_SCOPE = "ide_telemetry";
443
- exports.ASCENDA_PROVENANCE = "ai_work_telemetry";
444
- exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = "semantic_work_signals";
445
- exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = "workflow_telemetry";
446
- exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = "historical_import";
447
- exports.ASCENDA_SEMANTIC_PROVENANCE = "semantic_work_signals";
448
- }
449
- });
450
-
451
775
  // ../packages/tool-kit/out/eventLog.js
452
776
  var require_eventLog = __commonJS({
453
777
  "../packages/tool-kit/out/eventLog.js"(exports) {
@@ -548,6 +872,7 @@ var require_http = __commonJS({
548
872
  exports.postToolEvent = postToolEvent;
549
873
  exports.postToolEventsBatch = postToolEventsBatch;
550
874
  exports.parseIngestResponse = parseIngestResponse;
875
+ var tool_contract_1 = require_out();
551
876
  var AscendaApiError = class extends Error {
552
877
  status;
553
878
  errorCode;
@@ -590,6 +915,35 @@ var require_http = __commonJS({
590
915
  throw new AscendaApiError(response.status, void 0, await response.text());
591
916
  return await response.json();
592
917
  }
918
+ function isDeliveredStatus(value) {
919
+ return typeof value === "string" && tool_contract_1.TOOL_EVENT_DELIVERED_STATUSES.includes(value);
920
+ }
921
+ function readSuccessBody(body) {
922
+ let parsed;
923
+ try {
924
+ parsed = JSON.parse(body);
925
+ } catch {
926
+ return { duplicates: 0 };
927
+ }
928
+ if (!parsed || typeof parsed !== "object")
929
+ return { duplicates: 0 };
930
+ const single = parsed.status;
931
+ if (isDeliveredStatus(single))
932
+ return { duplicates: single === "duplicate" ? 1 : 0 };
933
+ const raw = parsed.results;
934
+ if (!Array.isArray(raw))
935
+ return { duplicates: 0 };
936
+ const results = [];
937
+ for (const item of raw) {
938
+ if (!item || typeof item !== "object")
939
+ continue;
940
+ const { index, status, reason } = item;
941
+ if (typeof index !== "number" || typeof status !== "string")
942
+ continue;
943
+ results.push({ index, status, ...typeof reason === "string" ? { reason } : {} });
944
+ }
945
+ return { duplicates: results.filter((item) => item.status === "duplicate").length, results };
946
+ }
593
947
  function isRetryableStatus(status) {
594
948
  return status === 408 || status === 429 || status !== void 0 && status >= 500 && status <= 599;
595
949
  }
@@ -616,8 +970,20 @@ var require_http = __commonJS({
616
970
  }
617
971
  }
618
972
  async function parseIngestResponse(response) {
619
- if (response.ok)
620
- return { result: "accepted", httpStatus: response.status };
973
+ if (response.ok) {
974
+ const outcome = { result: "accepted", httpStatus: response.status };
975
+ let read = { duplicates: 0 };
976
+ try {
977
+ read = readSuccessBody(await response.text());
978
+ } catch {
979
+ read = { duplicates: 0 };
980
+ }
981
+ return {
982
+ ...outcome,
983
+ ...read.duplicates > 0 ? { duplicates: read.duplicates } : {},
984
+ ...read.results !== void 0 ? { results: read.results } : {}
985
+ };
986
+ }
621
987
  const body = await response.text();
622
988
  let errorCode;
623
989
  try {
@@ -679,15 +1045,20 @@ var require_tokenStore = __commonJS({
679
1045
  };
680
1046
  }();
681
1047
  Object.defineProperty(exports, "__esModule", { value: true });
1048
+ exports.ascendaHome = ascendaHome;
682
1049
  exports.defaultTokenFilePath = defaultTokenFilePath2;
683
1050
  exports.persistEventWriteToken = persistEventWriteToken2;
1051
+ exports.listPersistedToolInstallationIds = listPersistedToolInstallationIds;
684
1052
  exports.readTokenFile = readTokenFile2;
685
1053
  exports.sanitizeFilePart = sanitizeFilePart;
686
1054
  var fs = __importStar(__require("fs"));
687
1055
  var os = __importStar(__require("os"));
688
1056
  var path = __importStar(__require("path"));
1057
+ function ascendaHome() {
1058
+ return process.env.ASCENDA_HOME ?? path.join(os.homedir(), ".ascenda");
1059
+ }
689
1060
  function defaultTokenFilePath2(toolInstallationId) {
690
- return path.join(os.homedir(), ".ascenda", "tokens", sanitizeFilePart(toolInstallationId));
1061
+ return path.join(ascendaHome(), "tokens", sanitizeFilePart(toolInstallationId));
691
1062
  }
692
1063
  function persistEventWriteToken2(tokenFilePath, token) {
693
1064
  const dir = path.dirname(tokenFilePath);
@@ -698,6 +1069,32 @@ var require_tokenStore = __commonJS({
698
1069
  fs.chmodSync(tokenFilePath, 384);
699
1070
  }
700
1071
  }
1072
+ function listPersistedToolInstallationIds(toolType) {
1073
+ const prefix = `${sanitizeFilePart(toolType)}_`;
1074
+ const dir = path.join(ascendaHome(), "tokens");
1075
+ let names;
1076
+ try {
1077
+ names = fs.readdirSync(dir);
1078
+ } catch {
1079
+ return [];
1080
+ }
1081
+ const ids = [];
1082
+ for (const name of names.sort()) {
1083
+ if (!name.startsWith(prefix) || name.length === prefix.length)
1084
+ continue;
1085
+ const file = path.join(dir, name);
1086
+ try {
1087
+ if (!fs.statSync(file).isFile())
1088
+ continue;
1089
+ } catch {
1090
+ continue;
1091
+ }
1092
+ if (readTokenFile2(file) === void 0)
1093
+ continue;
1094
+ ids.push(`${toolType}:${name.slice(prefix.length)}`);
1095
+ }
1096
+ return ids;
1097
+ }
701
1098
  function readTokenFile2(tokenFilePath) {
702
1099
  try {
703
1100
  if (!fs.existsSync(tokenFilePath))
@@ -757,8 +1154,11 @@ var require_stateStore = __commonJS({
757
1154
  }();
758
1155
  Object.defineProperty(exports, "__esModule", { value: true });
759
1156
  exports.defaultStateFilePath = defaultStateFilePath;
1157
+ exports.unresolvedToolInstallationId = unresolvedToolInstallationId;
1158
+ exports.unresolvedStateFilePath = unresolvedStateFilePath;
760
1159
  exports.readCollectorState = readCollectorState;
761
1160
  exports.recordSendOutcome = recordSendOutcome;
1161
+ exports.recordOutboxDiscard = recordOutboxDiscard;
762
1162
  exports.shouldAnnounceFailure = shouldAnnounceFailure;
763
1163
  exports.markFailureNotified = markFailureNotified;
764
1164
  var fs = __importStar(__require("fs"));
@@ -770,6 +1170,12 @@ var require_stateStore = __commonJS({
770
1170
  const base = dir ? dir : path.join(os.homedir(), ".ascenda", "state");
771
1171
  return path.join(base, `${(0, tokenStore_1.sanitizeFilePart)(toolInstallationId)}.json`);
772
1172
  }
1173
+ function unresolvedToolInstallationId(toolType) {
1174
+ return `${toolType}:unresolved`;
1175
+ }
1176
+ function unresolvedStateFilePath(toolType) {
1177
+ return defaultStateFilePath(unresolvedToolInstallationId(toolType));
1178
+ }
773
1179
  function readCollectorState(stateFilePath) {
774
1180
  try {
775
1181
  if (!fs.existsSync(stateFilePath))
@@ -802,7 +1208,28 @@ var require_stateStore = __commonJS({
802
1208
  // the one already open. Carrying `notifiedFailingSince` across a
803
1209
  // continuing episode is what keeps the notice to once per outage.
804
1210
  ...accepted ? {} : { failingSince: previous?.failingSince ?? now },
805
- ...accepted ? {} : previous?.notifiedFailingSince !== void 0 ? { notifiedFailingSince: previous.notifiedFailingSince } : {}
1211
+ ...accepted ? {} : previous?.notifiedFailingSince !== void 0 ? { notifiedFailingSince: previous.notifiedFailingSince } : {},
1212
+ // Cumulative by design: a send outcome, success included, never erases
1213
+ // the record of what the outbox had to throw away.
1214
+ ...previous?.outboxDiscarded !== void 0 ? { outboxDiscarded: previous.outboxDiscarded } : {}
1215
+ };
1216
+ writeStateFile(stateFilePath, next);
1217
+ return next;
1218
+ }
1219
+ function recordOutboxDiscard(stateFilePath, toolInstallationId, discard) {
1220
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1221
+ const previous = readCollectorState(stateFilePath);
1222
+ const next = {
1223
+ ...previous ?? { lastAttemptAt: now, consecutiveFailures: 0 },
1224
+ toolInstallationId,
1225
+ lastOutcome: "outbox_discarded",
1226
+ outboxDiscarded: {
1227
+ total: (previous?.outboxDiscarded?.total ?? 0) + discard.count,
1228
+ lastAt: now,
1229
+ lastCount: discard.count,
1230
+ lastReasons: discard.reasons,
1231
+ ...discard.oldestQueuedAt !== void 0 ? { lastOldestQueuedAt: discard.oldestQueuedAt } : {}
1232
+ }
806
1233
  };
807
1234
  writeStateFile(stateFilePath, next);
808
1235
  return next;
@@ -842,6 +1269,226 @@ var require_stateStore = __commonJS({
842
1269
  }
843
1270
  });
844
1271
 
1272
+ // ../packages/tool-kit/out/outbox.js
1273
+ var require_outbox = __commonJS({
1274
+ "../packages/tool-kit/out/outbox.js"(exports) {
1275
+ "use strict";
1276
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
1277
+ if (k2 === void 0) k2 = k;
1278
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1279
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1280
+ desc = { enumerable: true, get: function() {
1281
+ return m[k];
1282
+ } };
1283
+ }
1284
+ Object.defineProperty(o, k2, desc);
1285
+ } : function(o, m, k, k2) {
1286
+ if (k2 === void 0) k2 = k;
1287
+ o[k2] = m[k];
1288
+ });
1289
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
1290
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1291
+ } : function(o, v) {
1292
+ o["default"] = v;
1293
+ });
1294
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
1295
+ var ownKeys = function(o) {
1296
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
1297
+ var ar = [];
1298
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
1299
+ return ar;
1300
+ };
1301
+ return ownKeys(o);
1302
+ };
1303
+ return function(mod) {
1304
+ if (mod && mod.__esModule) return mod;
1305
+ var result = {};
1306
+ if (mod != null) {
1307
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1308
+ }
1309
+ __setModuleDefault(result, mod);
1310
+ return result;
1311
+ };
1312
+ }();
1313
+ Object.defineProperty(exports, "__esModule", { value: true });
1314
+ exports.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE = exports.DEFAULT_OUTBOX_MAX_AGE_MS = exports.DEFAULT_OUTBOX_MAX_ENTRIES = exports.OUTBOX_DRAIN_ENV_VAR = void 0;
1315
+ exports.outboxDrainEnabled = outboxDrainEnabled;
1316
+ exports.defaultOutboxFilePath = defaultOutboxFilePath;
1317
+ exports.appendToOutbox = appendToOutbox;
1318
+ exports.readOutboxSummary = readOutboxSummary;
1319
+ exports.claimOutbox = claimOutbox;
1320
+ exports.enforceOutboxBounds = enforceOutboxBounds;
1321
+ var fs = __importStar(__require("fs"));
1322
+ var path = __importStar(__require("path"));
1323
+ var stateStore_1 = require_stateStore();
1324
+ var tokenStore_1 = require_tokenStore();
1325
+ exports.OUTBOX_DRAIN_ENV_VAR = "ASCENDA_OUTBOX_DRAIN";
1326
+ exports.DEFAULT_OUTBOX_MAX_ENTRIES = 1e4;
1327
+ exports.DEFAULT_OUTBOX_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
1328
+ exports.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE = 100;
1329
+ var ORPHANED_CLAIM_AGE_MS = 6e4;
1330
+ var CLAIM_SUFFIX = ".draining";
1331
+ function outboxDrainEnabled(env = process.env) {
1332
+ const value = env[exports.OUTBOX_DRAIN_ENV_VAR]?.trim().toLowerCase();
1333
+ return value === "1" || value === "true" || value === "yes" || value === "on";
1334
+ }
1335
+ function defaultOutboxFilePath(toolInstallationId) {
1336
+ const dir = path.dirname((0, stateStore_1.defaultStateFilePath)(toolInstallationId));
1337
+ return path.join(dir, `${(0, tokenStore_1.sanitizeFilePart)(toolInstallationId)}.outbox.jsonl`);
1338
+ }
1339
+ function appendToOutbox(outboxFilePath, payload, now = /* @__PURE__ */ new Date()) {
1340
+ try {
1341
+ const dir = path.dirname(outboxFilePath);
1342
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
1343
+ const entry = { queuedAt: now.toISOString(), payload };
1344
+ fs.appendFileSync(outboxFilePath, `${JSON.stringify(entry)}
1345
+ `, { encoding: "utf8", mode: 384 });
1346
+ if (process.platform !== "win32")
1347
+ fs.chmodSync(outboxFilePath, 384);
1348
+ return true;
1349
+ } catch {
1350
+ return false;
1351
+ }
1352
+ }
1353
+ function readOutboxSummary(outboxFilePath) {
1354
+ const files = [outboxFilePath, ...listClaimFiles(outboxFilePath)].filter((file) => fs.existsSync(file));
1355
+ if (files.length === 0)
1356
+ return void 0;
1357
+ let depth = 0;
1358
+ let unreadableLines = 0;
1359
+ let oldestQueuedAt;
1360
+ for (const file of files) {
1361
+ const { entries, unreadable } = readEntries(file);
1362
+ depth += entries.length;
1363
+ unreadableLines += unreadable;
1364
+ for (const entry of entries) {
1365
+ if (oldestQueuedAt === void 0 || entry.queuedAt < oldestQueuedAt)
1366
+ oldestQueuedAt = entry.queuedAt;
1367
+ }
1368
+ }
1369
+ return { depth, unreadableLines, ...oldestQueuedAt !== void 0 ? { oldestQueuedAt } : {} };
1370
+ }
1371
+ function claimOutbox(outboxFilePath, now = Date.now()) {
1372
+ const claimPath = `${outboxFilePath}.${process.pid}${CLAIM_SUFFIX}`;
1373
+ const claimed = [];
1374
+ try {
1375
+ fs.renameSync(outboxFilePath, claimPath);
1376
+ claimed.push(claimPath);
1377
+ } catch {
1378
+ }
1379
+ let orphanIndex = 0;
1380
+ for (const orphan of listClaimFiles(outboxFilePath)) {
1381
+ if (claimed.includes(orphan))
1382
+ continue;
1383
+ try {
1384
+ if (now - fs.statSync(orphan).mtimeMs < ORPHANED_CLAIM_AGE_MS)
1385
+ continue;
1386
+ const mine = `${claimPath}.${orphanIndex++}`;
1387
+ fs.renameSync(orphan, mine);
1388
+ claimed.push(mine);
1389
+ } catch {
1390
+ }
1391
+ }
1392
+ if (claimed.length === 0)
1393
+ return void 0;
1394
+ const entries = [];
1395
+ let unreadable = 0;
1396
+ for (const file of claimed) {
1397
+ const read = readEntries(file);
1398
+ entries.push(...read.entries);
1399
+ unreadable += read.unreadable;
1400
+ }
1401
+ entries.sort((a, b) => a.queuedAt < b.queuedAt ? -1 : a.queuedAt > b.queuedAt ? 1 : 0);
1402
+ let released = false;
1403
+ return {
1404
+ entries,
1405
+ unreadable,
1406
+ release(remainder) {
1407
+ if (released)
1408
+ return;
1409
+ released = true;
1410
+ if (remainder.length > 0) {
1411
+ try {
1412
+ fs.mkdirSync(path.dirname(outboxFilePath), { recursive: true, mode: 448 });
1413
+ fs.appendFileSync(outboxFilePath, remainder.map((entry) => `${JSON.stringify(entry)}
1414
+ `).join(""), { encoding: "utf8", mode: 384 });
1415
+ if (process.platform !== "win32")
1416
+ fs.chmodSync(outboxFilePath, 384);
1417
+ } catch {
1418
+ return;
1419
+ }
1420
+ }
1421
+ for (const file of claimed) {
1422
+ try {
1423
+ fs.unlinkSync(file);
1424
+ } catch {
1425
+ }
1426
+ }
1427
+ }
1428
+ };
1429
+ }
1430
+ function enforceOutboxBounds(entries, bounds, now = Date.now()) {
1431
+ const reasons = {};
1432
+ let oldestQueuedAt;
1433
+ const cutoff = new Date(now - bounds.maxAgeMs).toISOString();
1434
+ const fresh = [];
1435
+ for (const entry of entries) {
1436
+ if (entry.queuedAt < cutoff) {
1437
+ reasons.age = (reasons.age ?? 0) + 1;
1438
+ if (oldestQueuedAt === void 0 || entry.queuedAt < oldestQueuedAt)
1439
+ oldestQueuedAt = entry.queuedAt;
1440
+ } else {
1441
+ fresh.push(entry);
1442
+ }
1443
+ }
1444
+ const excess = Math.max(0, fresh.length - bounds.maxEntries);
1445
+ if (excess > 0) {
1446
+ reasons.count = excess;
1447
+ const first = fresh[0]?.queuedAt;
1448
+ if (first !== void 0 && (oldestQueuedAt === void 0 || first < oldestQueuedAt))
1449
+ oldestQueuedAt = first;
1450
+ }
1451
+ const kept = excess > 0 ? fresh.slice(excess) : fresh;
1452
+ const count = Object.values(reasons).reduce((sum, n) => sum + (n ?? 0), 0);
1453
+ return { kept, discarded: { count, reasons, ...oldestQueuedAt !== void 0 ? { oldestQueuedAt } : {} } };
1454
+ }
1455
+ function listClaimFiles(outboxFilePath) {
1456
+ const dir = path.dirname(outboxFilePath);
1457
+ const prefix = `${path.basename(outboxFilePath)}.`;
1458
+ try {
1459
+ return fs.readdirSync(dir).filter((name) => name.startsWith(prefix) && name.includes(CLAIM_SUFFIX)).map((name) => path.join(dir, name)).sort();
1460
+ } catch {
1461
+ return [];
1462
+ }
1463
+ }
1464
+ function readEntries(file) {
1465
+ let raw;
1466
+ try {
1467
+ raw = fs.readFileSync(file, "utf8");
1468
+ } catch {
1469
+ return { entries: [], unreadable: 0 };
1470
+ }
1471
+ const entries = [];
1472
+ let unreadable = 0;
1473
+ for (const line of raw.split("\n")) {
1474
+ if (!line.trim())
1475
+ continue;
1476
+ try {
1477
+ const parsed = JSON.parse(line);
1478
+ if (!parsed || typeof parsed !== "object" || typeof parsed.queuedAt !== "string" || !parsed.payload || typeof parsed.payload !== "object") {
1479
+ unreadable += 1;
1480
+ continue;
1481
+ }
1482
+ entries.push({ queuedAt: parsed.queuedAt, payload: parsed.payload });
1483
+ } catch {
1484
+ unreadable += 1;
1485
+ }
1486
+ }
1487
+ return { entries, unreadable };
1488
+ }
1489
+ }
1490
+ });
1491
+
845
1492
  // ../packages/tool-kit/out/eventSender.js
846
1493
  var require_eventSender = __commonJS({
847
1494
  "../packages/tool-kit/out/eventSender.js"(exports) {
@@ -853,8 +1500,10 @@ var require_eventSender = __commonJS({
853
1500
  var tool_contract_1 = require_out();
854
1501
  var eventLog_1 = require_eventLog();
855
1502
  var http_1 = require_http();
1503
+ var outbox_1 = require_outbox();
856
1504
  var tokenStore_1 = require_tokenStore();
857
1505
  var stateStore_1 = require_stateStore();
1506
+ var payload_1 = require_payload();
858
1507
  var AscendaSemanticEventError = class extends Error {
859
1508
  constructor(message) {
860
1509
  super(message);
@@ -867,6 +1516,7 @@ var require_eventSender = __commonJS({
867
1516
  toolInstallationId: identity.toolInstallationId,
868
1517
  source: identity.source,
869
1518
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
1519
+ idempotencyKey: (0, payload_1.mintIdempotencyKey)(),
870
1520
  utcOffsetMinutes: (0, afterHours_1.utcOffsetMinutesAt)(/* @__PURE__ */ new Date()),
871
1521
  sessionId: identity.sessionId ?? void 0,
872
1522
  workspaceHash: identity.workspaceHash ?? void 0,
@@ -884,6 +1534,9 @@ var require_eventSender = __commonJS({
884
1534
  config;
885
1535
  eventWriteToken;
886
1536
  lastState;
1537
+ lastDrain;
1538
+ /** One outbox pass per sender, i.e. per hook process. The hook is on the user's critical path. */
1539
+ outboxServiced = false;
887
1540
  constructor(config2) {
888
1541
  this.config = config2;
889
1542
  this.eventWriteToken = config2.eventWriteToken;
@@ -922,6 +1575,7 @@ var require_eventSender = __commonJS({
922
1575
  source: this.config.source,
923
1576
  eventType: mapped.eventType,
924
1577
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
1578
+ idempotencyKey: (0, payload_1.mintIdempotencyKey)(),
925
1579
  utcOffsetMinutes: (0, afterHours_1.utcOffsetMinutesAt)(/* @__PURE__ */ new Date()),
926
1580
  severity: "low",
927
1581
  sessionId: this.config.sessionId ?? void 0,
@@ -952,6 +1606,7 @@ var require_eventSender = __commonJS({
952
1606
  source: this.config.source,
953
1607
  eventType: mapped.eventType,
954
1608
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
1609
+ idempotencyKey: (0, payload_1.mintIdempotencyKey)(),
955
1610
  utcOffsetMinutes: (0, afterHours_1.utcOffsetMinutesAt)(/* @__PURE__ */ new Date()),
956
1611
  severity: "low",
957
1612
  sessionId: this.config.sessionId ?? void 0,
@@ -970,15 +1625,30 @@ var require_eventSender = __commonJS({
970
1625
  * deliberate: Claude Code, Codex, the GitHub collector and the MCP server all
971
1626
  * send through this method, and the defect being fixed showed up in three
972
1627
  * separate components because each was left to notice its own failures.
1628
+ *
1629
+ * The outbox is serviced first, once per process. If that pass just watched
1630
+ * the ingest door refuse a batch, the live event is not offered to the same
1631
+ * door a second time in the same instant: it inherits the pass's outcome,
1632
+ * and a retryable one puts it straight in the queue. That is what keeps a
1633
+ * hook during an outage to one bounded round trip instead of three.
973
1634
  */
974
1635
  async post(payload) {
975
- const outcome = await this.attempt(payload);
1636
+ const halted = await this.serviceOutbox();
1637
+ let outcome;
1638
+ let queued = false;
1639
+ if (halted) {
1640
+ outcome = halted;
1641
+ queued = this.isRetryable(outcome) && this.enqueue(payload);
1642
+ } else {
1643
+ outcome = await this.attempt(payload);
1644
+ queued = this.isRetryable(outcome) && this.enqueue(payload);
1645
+ }
976
1646
  this.lastState = (0, stateStore_1.recordSendOutcome)(this.stateFilePath(), this.config.toolInstallationId, outcome.result, {
977
1647
  httpStatus: outcome.httpStatus,
978
1648
  errorCode: outcome.errorCode,
979
- detail: outcome.detail
1649
+ detail: queued ? withNote(outcome.detail, "queued in outbox") : outcome.detail
980
1650
  });
981
- this.log(payload, outcome.result);
1651
+ this.log(payload, outcome.result, queued ? "queued" : void 0);
982
1652
  return outcome.result;
983
1653
  }
984
1654
  /**
@@ -987,6 +1657,15 @@ var require_eventSender = __commonJS({
987
1657
  * error gets one retry, because the common cases (a restarting instance, a
988
1658
  * proxy blip, a 429) clear in well under a second and the alternative is
989
1659
  * losing the event outright.
1660
+ *
1661
+ * Both recoveries resend the same `payload` object, so the `idempotencyKey`
1662
+ * minted at construction is what the server sees on every attempt. That is
1663
+ * what lets a retry of a request the server actually processed (a timeout
1664
+ * after the write, a 502 from a proxy in front of a 200) come back
1665
+ * `duplicate` instead of landing twice. Never rebuild the payload here.
1666
+ *
1667
+ * When the retry fails too, the caller queues the payload: anything longer
1668
+ * than the pause here is the outbox's job, not another in-process wait.
990
1669
  */
991
1670
  async attempt(payload) {
992
1671
  const outcome = await (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
@@ -995,12 +1674,138 @@ var require_eventSender = __commonJS({
995
1674
  return outcome;
996
1675
  return (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
997
1676
  }
998
- if (outcome.result === "transport_error" && (outcome.httpStatus === void 0 || (0, http_1.isRetryableStatus)(outcome.httpStatus))) {
1677
+ if (this.isRetryable(outcome)) {
999
1678
  await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
1000
1679
  return (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
1001
1680
  }
1002
1681
  return outcome;
1003
1682
  }
1683
+ /** A failure that never reached a verdict. Replaying can change the answer. */
1684
+ isRetryable(outcome) {
1685
+ return outcome.result === "transport_error" && (outcome.httpStatus === void 0 || (0, http_1.isRetryableStatus)(outcome.httpStatus));
1686
+ }
1687
+ /**
1688
+ * Keeps a refused payload for a later drain. Returns whether it is now on
1689
+ * disk; when it is not (read-only home, full disk) the event is lost and the
1690
+ * journal's detail says so instead of implying it was kept.
1691
+ */
1692
+ enqueue(payload) {
1693
+ return (0, outbox_1.appendToOutbox)(this.outboxFilePath(), payload);
1694
+ }
1695
+ /**
1696
+ * One pass over the outbox: claim it, apply the bounds, and — when sending
1697
+ * is enabled — offer one batch, oldest first, to the batch door.
1698
+ *
1699
+ * Entries are deleted on `accepted` or `duplicate`, decided on `status`
1700
+ * alone; `reason` is for a person reading their logs. A per-item `rejected`
1701
+ * is a verdict, and replaying a verdict cannot change it, so those are
1702
+ * discarded and journaled rather than kept forever. A whole-batch
1703
+ * `validation_failed` is the same verdict for every item. Anything else
1704
+ * stops the pass with everything still on disk, and is returned so the live
1705
+ * send can skip a door that just refused.
1706
+ *
1707
+ * Never loops, never backs off, never sends more than one batch: the next
1708
+ * hook invocation is usually seconds away, and a hook sitting in a retry
1709
+ * loop delays the tool call the user is waiting on.
1710
+ */
1711
+ async serviceOutbox() {
1712
+ if (this.outboxServiced)
1713
+ return void 0;
1714
+ this.outboxServiced = true;
1715
+ const sendEnabled = this.config.outboxDrain ?? (0, outbox_1.outboxDrainEnabled)();
1716
+ const claimed = (0, outbox_1.claimOutbox)(this.outboxFilePath());
1717
+ if (!claimed) {
1718
+ this.lastDrain = { found: 0, discarded: 0, delivered: 0, remaining: 0, sendEnabled };
1719
+ return void 0;
1720
+ }
1721
+ const found = claimed.entries.length + claimed.unreadable;
1722
+ const { kept, discarded } = (0, outbox_1.enforceOutboxBounds)(claimed.entries, {
1723
+ maxEntries: this.config.outboxMaxEntries ?? outbox_1.DEFAULT_OUTBOX_MAX_ENTRIES,
1724
+ maxAgeMs: this.config.outboxMaxAgeMs ?? outbox_1.DEFAULT_OUTBOX_MAX_AGE_MS
1725
+ });
1726
+ if (claimed.unreadable > 0) {
1727
+ discarded.count += claimed.unreadable;
1728
+ discarded.reasons.unreadable = claimed.unreadable;
1729
+ }
1730
+ let discardedTotal = this.journalDiscard(discarded);
1731
+ if (!sendEnabled || kept.length === 0) {
1732
+ claimed.release(kept);
1733
+ this.lastDrain = { found, discarded: discardedTotal, delivered: 0, remaining: kept.length, sendEnabled };
1734
+ return void 0;
1735
+ }
1736
+ const batchSize = this.config.outboxDrainBatchSize ?? outbox_1.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE;
1737
+ const batch = kept.slice(0, batchSize);
1738
+ const rest = kept.slice(batchSize);
1739
+ const outcome = await this.attemptBatch(batch.map((entry) => entry.payload));
1740
+ let delivered = [];
1741
+ let rejected = [];
1742
+ let undecided = [];
1743
+ let halted;
1744
+ if (outcome.result === "accepted") {
1745
+ if (outcome.results === void 0) {
1746
+ delivered = batch;
1747
+ } else {
1748
+ const byIndex = new Map(outcome.results.map((item) => [item.index, item.status]));
1749
+ for (const [index, entry] of batch.entries()) {
1750
+ const status = byIndex.get(index);
1751
+ if (status !== void 0 && tool_contract_1.TOOL_EVENT_DELIVERED_STATUSES.includes(status))
1752
+ delivered.push(entry);
1753
+ else if (status === "rejected")
1754
+ rejected.push(entry);
1755
+ else
1756
+ undecided.push(entry);
1757
+ }
1758
+ }
1759
+ } else if (outcome.result === "validation_failed") {
1760
+ rejected = batch;
1761
+ } else {
1762
+ undecided = batch;
1763
+ halted = outcome;
1764
+ }
1765
+ if (rejected.length > 0) {
1766
+ discardedTotal += this.journalDiscard({ count: rejected.length, reasons: { rejected: rejected.length }, oldestQueuedAt: rejected[0]?.queuedAt });
1767
+ }
1768
+ if (!halted) {
1769
+ this.lastState = (0, stateStore_1.recordSendOutcome)(this.stateFilePath(), this.config.toolInstallationId, outcome.result, {
1770
+ httpStatus: outcome.httpStatus,
1771
+ errorCode: outcome.errorCode,
1772
+ detail: withNote(outcome.detail, `outbox drain: ${delivered.length} delivered`)
1773
+ });
1774
+ }
1775
+ for (const entry of delivered)
1776
+ this.log(entry.payload, "accepted", "drained");
1777
+ const remainder = [...undecided, ...rest];
1778
+ claimed.release(remainder);
1779
+ this.lastDrain = {
1780
+ found,
1781
+ discarded: discardedTotal,
1782
+ delivered: delivered.length,
1783
+ remaining: remainder.length,
1784
+ sendEnabled,
1785
+ ...halted ? { halted: halted.result } : {}
1786
+ };
1787
+ return halted;
1788
+ }
1789
+ /** The batch door, with the same single token renewal as the live path and no in-process retry. */
1790
+ async attemptBatch(payloads) {
1791
+ const outcome = await (0, http_1.postToolEventsBatch)(this.config.apiBaseUrl, this.eventWriteToken, payloads, this.signal());
1792
+ if (outcome.result !== "auth_failed")
1793
+ return outcome;
1794
+ if (!await this.renewEventToken())
1795
+ return outcome;
1796
+ return (0, http_1.postToolEventsBatch)(this.config.apiBaseUrl, this.eventWriteToken, payloads, this.signal());
1797
+ }
1798
+ journalDiscard(discard) {
1799
+ if (discard.count === 0)
1800
+ return 0;
1801
+ const reasons = discard.reasons;
1802
+ this.lastState = (0, stateStore_1.recordOutboxDiscard)(this.stateFilePath(), this.config.toolInstallationId, {
1803
+ count: discard.count,
1804
+ reasons,
1805
+ oldestQueuedAt: discard.oldestQueuedAt
1806
+ });
1807
+ return discard.count;
1808
+ }
1004
1809
  /**
1005
1810
  * The state written by the most recent send, so a caller can decide whether
1006
1811
  * to surface a one-time notice without re-reading the journal it just wrote.
@@ -1008,10 +1813,16 @@ var require_eventSender = __commonJS({
1008
1813
  get state() {
1009
1814
  return this.lastState;
1010
1815
  }
1816
+ /** What this sender's one outbox pass did; undefined before the first send. */
1817
+ get drain() {
1818
+ return this.lastDrain;
1819
+ }
1011
1820
  stateFilePath() {
1012
1821
  return this.config.stateFilePath ?? (0, stateStore_1.defaultStateFilePath)(this.config.toolInstallationId);
1013
1822
  }
1014
- /** Never throws: a renewal that errors is a failed renewal, not a failed turn. */
1823
+ outboxFilePath() {
1824
+ return this.config.outboxFilePath ?? (0, outbox_1.defaultOutboxFilePath)(this.config.toolInstallationId);
1825
+ }
1015
1826
  /**
1016
1827
  * Every send path funnels through {@link post}, so semantic and
1017
1828
  * collaboration signals are logged on the same terms as host events — the
@@ -1021,12 +1832,13 @@ var require_eventSender = __commonJS({
1021
1832
  * It is now `transport_error` through the ordinary path, because the
1022
1833
  * transport returns that outcome instead of throwing.
1023
1834
  */
1024
- log(payload, delivery) {
1835
+ log(payload, delivery, outbox) {
1025
1836
  const logFile = this.config.eventLogFile === void 0 ? (0, eventLog_1.resolveEventLogPath)() : this.config.eventLogFile;
1026
1837
  if (!logFile)
1027
1838
  return;
1028
- (0, eventLog_1.appendEventLog)(logFile, { loggedAt: (/* @__PURE__ */ new Date()).toISOString(), delivery, payload });
1839
+ (0, eventLog_1.appendEventLog)(logFile, { loggedAt: (/* @__PURE__ */ new Date()).toISOString(), delivery, payload, ...outbox ? { outbox } : {} });
1029
1840
  }
1841
+ /** Never throws: a renewal that errors is a failed renewal, not a failed turn. */
1030
1842
  async renewEventToken() {
1031
1843
  try {
1032
1844
  const renewed = await (0, http_1.renewToolToken)(this.config.apiBaseUrl, this.eventWriteToken, this.signal());
@@ -1044,6 +1856,9 @@ var require_eventSender = __commonJS({
1044
1856
  }
1045
1857
  };
1046
1858
  exports.AscendaEventSender = AscendaEventSender2;
1859
+ function withNote(detail, note) {
1860
+ return detail ? `${detail} (${note})` : note;
1861
+ }
1047
1862
  }
1048
1863
  });
1049
1864
 
@@ -1171,21 +1986,284 @@ var require_contextRegistry = __commonJS({
1171
1986
  writeRegistry(registryFilePath, registry2);
1172
1987
  return true;
1173
1988
  } catch {
1174
- return false;
1989
+ return false;
1990
+ }
1991
+ }
1992
+ function dayOf(iso) {
1993
+ return iso.slice(0, 10);
1994
+ }
1995
+ function writeRegistry(registryFilePath, registry2) {
1996
+ const dir = path.dirname(registryFilePath);
1997
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
1998
+ const tmp = `${registryFilePath}.${process.pid}.tmp`;
1999
+ fs.writeFileSync(tmp, `${JSON.stringify(registry2, null, 2)}
2000
+ `, { encoding: "utf8", mode: 384 });
2001
+ fs.renameSync(tmp, registryFilePath);
2002
+ if (process.platform !== "win32") {
2003
+ fs.chmodSync(registryFilePath, 384);
2004
+ }
2005
+ }
2006
+ }
2007
+ });
2008
+
2009
+ // ../packages/tool-kit/out/credentials.js
2010
+ var require_credentials = __commonJS({
2011
+ "../packages/tool-kit/out/credentials.js"(exports) {
2012
+ "use strict";
2013
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
2014
+ if (k2 === void 0) k2 = k;
2015
+ var desc = Object.getOwnPropertyDescriptor(m, k);
2016
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2017
+ desc = { enumerable: true, get: function() {
2018
+ return m[k];
2019
+ } };
2020
+ }
2021
+ Object.defineProperty(o, k2, desc);
2022
+ } : function(o, m, k, k2) {
2023
+ if (k2 === void 0) k2 = k;
2024
+ o[k2] = m[k];
2025
+ });
2026
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
2027
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
2028
+ } : function(o, v) {
2029
+ o["default"] = v;
2030
+ });
2031
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
2032
+ var ownKeys = function(o) {
2033
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
2034
+ var ar = [];
2035
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
2036
+ return ar;
2037
+ };
2038
+ return ownKeys(o);
2039
+ };
2040
+ return function(mod) {
2041
+ if (mod && mod.__esModule) return mod;
2042
+ var result = {};
2043
+ if (mod != null) {
2044
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
2045
+ }
2046
+ __setModuleDefault(result, mod);
2047
+ return result;
2048
+ };
2049
+ }();
2050
+ Object.defineProperty(exports, "__esModule", { value: true });
2051
+ exports.credentialsFilePath = credentialsFilePath;
2052
+ exports.readMachineCredentials = readMachineCredentials;
2053
+ exports.writeMachineCredentials = writeMachineCredentials;
2054
+ exports.writeTopLevelCredentials = writeTopLevelCredentials;
2055
+ exports.readHostCredentials = readHostCredentials;
2056
+ exports.writeHostCredentials = writeHostCredentials;
2057
+ exports.removeHostCredentials = removeHostCredentials;
2058
+ var fs = __importStar(__require("fs"));
2059
+ var path = __importStar(__require("path"));
2060
+ var tokenStore_1 = require_tokenStore();
2061
+ function credentialsFilePath() {
2062
+ return path.join((0, tokenStore_1.ascendaHome)(), "credentials.json");
2063
+ }
2064
+ function readMachineCredentials() {
2065
+ try {
2066
+ const raw = fs.readFileSync(credentialsFilePath(), "utf8").trim();
2067
+ if (!raw)
2068
+ return void 0;
2069
+ const parsed = JSON.parse(raw);
2070
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
2071
+ return void 0;
2072
+ return parsed;
2073
+ } catch {
2074
+ return void 0;
2075
+ }
2076
+ }
2077
+ function writeMachineCredentials(credentials) {
2078
+ const file = credentialsFilePath();
2079
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 448 });
2080
+ fs.writeFileSync(file, `${JSON.stringify(credentials, null, 2)}
2081
+ `, { encoding: "utf8", mode: 384 });
2082
+ if (process.platform !== "win32") {
2083
+ fs.chmodSync(path.dirname(file), 448);
2084
+ fs.chmodSync(file, 384);
2085
+ }
2086
+ }
2087
+ function writeTopLevelCredentials(credentials) {
2088
+ const existing = readMachineCredentials();
2089
+ writeMachineCredentials({ ...credentials, ...existing?.tools ? { tools: existing.tools } : {} });
2090
+ }
2091
+ function readHostCredentials(host) {
2092
+ const entry = readMachineCredentials()?.tools?.[host];
2093
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
2094
+ return void 0;
2095
+ return entry;
2096
+ }
2097
+ function writeHostCredentials(host, credentials) {
2098
+ const existing = readMachineCredentials() ?? {};
2099
+ writeMachineCredentials({ ...existing, tools: { ...existing.tools ?? {}, [host]: credentials } });
2100
+ }
2101
+ function removeHostCredentials(host) {
2102
+ const existing = readMachineCredentials();
2103
+ if (!existing?.tools || !(host in existing.tools))
2104
+ return;
2105
+ const tools = { ...existing.tools };
2106
+ delete tools[host];
2107
+ const next = { ...existing };
2108
+ if (Object.keys(tools).length)
2109
+ next.tools = tools;
2110
+ else
2111
+ delete next.tools;
2112
+ writeMachineCredentials(next);
2113
+ }
2114
+ }
2115
+ });
2116
+
2117
+ // ../packages/tool-kit/out/forgeProject.js
2118
+ var require_forgeProject = __commonJS({
2119
+ "../packages/tool-kit/out/forgeProject.js"(exports) {
2120
+ "use strict";
2121
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
2122
+ if (k2 === void 0) k2 = k;
2123
+ var desc = Object.getOwnPropertyDescriptor(m, k);
2124
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2125
+ desc = { enumerable: true, get: function() {
2126
+ return m[k];
2127
+ } };
2128
+ }
2129
+ Object.defineProperty(o, k2, desc);
2130
+ } : function(o, m, k, k2) {
2131
+ if (k2 === void 0) k2 = k;
2132
+ o[k2] = m[k];
2133
+ });
2134
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
2135
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
2136
+ } : function(o, v) {
2137
+ o["default"] = v;
2138
+ });
2139
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
2140
+ var ownKeys = function(o) {
2141
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
2142
+ var ar = [];
2143
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
2144
+ return ar;
2145
+ };
2146
+ return ownKeys(o);
2147
+ };
2148
+ return function(mod) {
2149
+ if (mod && mod.__esModule) return mod;
2150
+ var result = {};
2151
+ if (mod != null) {
2152
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
2153
+ }
2154
+ __setModuleDefault(result, mod);
2155
+ return result;
2156
+ };
2157
+ }();
2158
+ Object.defineProperty(exports, "__esModule", { value: true });
2159
+ exports.forgeProjectHash = forgeProjectHash;
2160
+ exports.parseForgeFullName = parseForgeFullName;
2161
+ exports.readForgeFullName = readForgeFullName;
2162
+ exports.forgeFullNameFromConfig = forgeFullNameFromConfig;
2163
+ exports.recordForgeProjectAlias = recordForgeProjectAlias;
2164
+ var fs = __importStar(__require("fs"));
2165
+ var path = __importStar(__require("path"));
2166
+ var contextRegistry_1 = require_contextRegistry();
2167
+ function forgeProjectHash(value) {
2168
+ let h = 2166136261;
2169
+ for (let i = 0; i < value.length; i++) {
2170
+ h ^= value.charCodeAt(i);
2171
+ h = Math.imul(h, 16777619) >>> 0;
2172
+ }
2173
+ return h.toString(16).padStart(8, "0");
2174
+ }
2175
+ function parseForgeFullName(remoteUrl) {
2176
+ if (!remoteUrl)
2177
+ return null;
2178
+ const trimmed = remoteUrl.trim();
2179
+ if (!trimmed)
2180
+ return null;
2181
+ const scp = /^(?:[^@/]+@)?([^/:]+):(.+)$/.exec(trimmed);
2182
+ const scheme = /^([a-z][a-z0-9+.-]*):\/\/(?:[^@/]*@)?([^/:]+)(?::\d+)?\/(.+)$/i.exec(trimmed);
2183
+ let host;
2184
+ let repoPath;
2185
+ if (scheme) {
2186
+ host = scheme[2];
2187
+ repoPath = scheme[3];
2188
+ } else if (scp && !/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
2189
+ host = scp[1];
2190
+ repoPath = scp[2];
2191
+ } else {
2192
+ return null;
2193
+ }
2194
+ const normalizedHost = host.toLowerCase().replace(/^www\./, "");
2195
+ if (normalizedHost !== "github.com")
2196
+ return null;
2197
+ const segments = repoPath.split("/").filter((segment) => segment.length > 0);
2198
+ if (segments.length < 2)
2199
+ return null;
2200
+ const owner = segments[0];
2201
+ const repo = segments[1].replace(/\.git$/, "");
2202
+ if (!owner || !repo)
2203
+ return null;
2204
+ return `${owner}/${repo}`;
2205
+ }
2206
+ function readForgeFullName(repositoryRoot) {
2207
+ if (!repositoryRoot)
2208
+ return null;
2209
+ let config2;
2210
+ try {
2211
+ config2 = fs.readFileSync(path.join(repositoryRoot, ".git", "config"), "utf8");
2212
+ } catch {
2213
+ return null;
1175
2214
  }
2215
+ return forgeFullNameFromConfig(config2);
1176
2216
  }
1177
- function dayOf(iso) {
1178
- return iso.slice(0, 10);
2217
+ function forgeFullNameFromConfig(config2) {
2218
+ const remotes = /* @__PURE__ */ new Map();
2219
+ let currentRemote = null;
2220
+ for (const rawLine of config2.split(/\r?\n/)) {
2221
+ const line = rawLine.trim();
2222
+ if (!line || line.startsWith("#") || line.startsWith(";"))
2223
+ continue;
2224
+ const section = /^\[([^\]]*)\]$/.exec(line);
2225
+ if (section) {
2226
+ const remote = /^remote\s+"(.*)"$/.exec(section[1].trim());
2227
+ currentRemote = remote ? remote[1] : null;
2228
+ continue;
2229
+ }
2230
+ if (!currentRemote)
2231
+ continue;
2232
+ const entry = /^url\s*=\s*(.*)$/.exec(line);
2233
+ if (entry && !remotes.has(currentRemote))
2234
+ remotes.set(currentRemote, entry[1].trim());
2235
+ }
2236
+ const ordered = [
2237
+ ...remotes.has("origin") ? ["origin"] : [],
2238
+ ...remotes.has("upstream") ? ["upstream"] : [],
2239
+ ...[...remotes.keys()].filter((name) => name !== "origin" && name !== "upstream")
2240
+ ];
2241
+ for (const name of ordered) {
2242
+ const fullName = parseForgeFullName(remotes.get(name));
2243
+ if (fullName)
2244
+ return fullName;
2245
+ }
2246
+ return null;
1179
2247
  }
1180
- function writeRegistry(registryFilePath, registry2) {
1181
- const dir = path.dirname(registryFilePath);
1182
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
1183
- const tmp = `${registryFilePath}.${process.pid}.tmp`;
1184
- fs.writeFileSync(tmp, `${JSON.stringify(registry2, null, 2)}
1185
- `, { encoding: "utf8", mode: 384 });
1186
- fs.renameSync(tmp, registryFilePath);
1187
- if (process.platform !== "win32") {
1188
- fs.chmodSync(registryFilePath, 384);
2248
+ function recordForgeProjectAlias(context, options) {
2249
+ try {
2250
+ if (!context?.projectHash || !context.projectLabel || !context.projectPath)
2251
+ return false;
2252
+ const fullName = readForgeFullName(context.projectPath);
2253
+ if (!fullName)
2254
+ return false;
2255
+ const variants = [fullName, fullName.toLowerCase()].filter((value, index, all) => all.indexOf(value) === index);
2256
+ let wrote = false;
2257
+ for (const variant of variants) {
2258
+ const hash = forgeProjectHash(variant);
2259
+ if (hash === context.projectHash || hash === context.workspaceHash)
2260
+ continue;
2261
+ if ((0, contextRegistry_1.recordWorkContextAlias)(hash, context.projectLabel, context.projectPath, options))
2262
+ wrote = true;
2263
+ }
2264
+ return wrote;
2265
+ } catch {
2266
+ return false;
1189
2267
  }
1190
2268
  }
1191
2269
  }
@@ -1317,6 +2395,10 @@ var require_workContext = __commonJS({
1317
2395
  }();
1318
2396
  Object.defineProperty(exports, "__esModule", { value: true });
1319
2397
  exports.deriveWorkContext = deriveWorkContext;
2398
+ exports.normalizeBranchName = normalizeBranchName;
2399
+ exports.deriveBranchHash = deriveBranchHash;
2400
+ exports.readBranchName = readBranchName;
2401
+ exports.deriveBranchHashForCwd = deriveBranchHashForCwd;
1320
2402
  var fs = __importStar(__require("fs"));
1321
2403
  var path = __importStar(__require("path"));
1322
2404
  var salt_1 = require_salt();
@@ -1331,6 +2413,8 @@ var require_workContext = __commonJS({
1331
2413
  } catch {
1332
2414
  roots = null;
1333
2415
  }
2416
+ if (!roots)
2417
+ roots = inferRootsFromPath(startPath);
1334
2418
  const workspacePath = roots?.checkoutRoot ?? startPath;
1335
2419
  const workspaceLabel = basenameOf(workspacePath);
1336
2420
  if (!workspaceLabel)
@@ -1359,9 +2443,12 @@ var require_workContext = __commonJS({
1359
2443
  stat = null;
1360
2444
  }
1361
2445
  if (stat?.isDirectory())
1362
- return { checkoutRoot: dir, canonicalRoot: dir };
1363
- if (stat?.isFile())
1364
- return { checkoutRoot: dir, canonicalRoot: worktreeParentRoot(dotGit, dir) ?? dir };
2446
+ return { checkoutRoot: dir, canonicalRoot: dir, gitDir: dotGit };
2447
+ if (stat?.isFile()) {
2448
+ const gitDir = readGitdirPointer(dotGit, dir);
2449
+ const canonicalRoot = (gitDir ? worktreeParentRoot(gitDir) : null) ?? dir;
2450
+ return { checkoutRoot: dir, canonicalRoot, gitDir };
2451
+ }
1365
2452
  const parent = path.dirname(dir);
1366
2453
  if (parent === dir)
1367
2454
  return null;
@@ -1369,22 +2456,45 @@ var require_workContext = __commonJS({
1369
2456
  }
1370
2457
  return null;
1371
2458
  }
1372
- function worktreeParentRoot(dotGitFile, containingDir) {
1373
- let gitdir;
2459
+ function readGitdirPointer(dotGitFile, containingDir) {
1374
2460
  try {
1375
2461
  const match = /^gitdir:\s*(.+)\s*$/m.exec(fs.readFileSync(dotGitFile, "utf8"));
1376
2462
  if (!match)
1377
2463
  return null;
1378
- gitdir = match[1].trim();
2464
+ return path.resolve(containingDir, match[1].trim());
1379
2465
  } catch {
1380
2466
  return null;
1381
2467
  }
1382
- const resolved = path.resolve(containingDir, gitdir);
2468
+ }
2469
+ function worktreeParentRoot(resolvedGitDir) {
1383
2470
  const marker = `${path.sep}.git${path.sep}worktrees${path.sep}`;
1384
- const idx = resolved.indexOf(marker);
2471
+ const idx = resolvedGitDir.indexOf(marker);
1385
2472
  if (idx === -1)
1386
2473
  return null;
1387
- return resolved.slice(0, idx);
2474
+ return resolvedGitDir.slice(0, idx);
2475
+ }
2476
+ function inferRootsFromPath(startPath) {
2477
+ const sep = startPath.includes("\\") && !startPath.includes("/") ? "\\" : "/";
2478
+ const leading = /^[\\/]/.test(startPath) ? sep : "";
2479
+ const segments = startPath.split(/[\\/]/).filter(Boolean);
2480
+ const join = (count) => leading + segments.slice(0, count).join(sep);
2481
+ for (let i = 0; i + 2 < segments.length; i++) {
2482
+ if (segments[i] === ".claude" && segments[i + 1] === "worktrees") {
2483
+ if (i === 0)
2484
+ return null;
2485
+ return { checkoutRoot: join(i + 3), canonicalRoot: join(i), gitDir: null };
2486
+ }
2487
+ }
2488
+ for (let i = 0; i + 1 < segments.length; i++) {
2489
+ const folder = segments[i];
2490
+ const suffix = ["-worktrees", "-wt"].find((s) => folder.endsWith(s) && folder.length > s.length);
2491
+ if (!suffix)
2492
+ continue;
2493
+ const repoName = folder.slice(0, -suffix.length);
2494
+ const canonicalRoot = leading + [...segments.slice(0, i), repoName].join(sep);
2495
+ return { checkoutRoot: join(i + 2), canonicalRoot, gitDir: null };
2496
+ }
2497
+ return null;
1388
2498
  }
1389
2499
  function stripTrailingSeparators(value) {
1390
2500
  let end = value.length;
@@ -1396,6 +2506,55 @@ var require_workContext = __commonJS({
1396
2506
  const segment = value.split(/[\\/]/).filter(Boolean).pop() ?? null;
1397
2507
  return segment && segment.length > 0 ? segment : null;
1398
2508
  }
2509
+ var REFS_HEADS_PREFIX = "refs/heads/";
2510
+ function normalizeBranchName(branch) {
2511
+ if (!branch)
2512
+ return null;
2513
+ let name = branch.trim();
2514
+ if (name.startsWith(REFS_HEADS_PREFIX))
2515
+ name = name.slice(REFS_HEADS_PREFIX.length).trim();
2516
+ if (!name || name === "HEAD")
2517
+ return null;
2518
+ return name;
2519
+ }
2520
+ function deriveBranchHash(branch, saltFilePath) {
2521
+ const name = normalizeBranchName(branch);
2522
+ if (!name)
2523
+ return null;
2524
+ try {
2525
+ return (0, salt_1.hashWithMachineSalt)(name, saltFilePath);
2526
+ } catch {
2527
+ return null;
2528
+ }
2529
+ }
2530
+ function readBranchName(cwd) {
2531
+ if (!cwd || !cwd.trim())
2532
+ return null;
2533
+ let gitDir = null;
2534
+ try {
2535
+ gitDir = resolveRepositoryRoots(stripTrailingSeparators(cwd.trim()))?.gitDir ?? null;
2536
+ } catch {
2537
+ gitDir = null;
2538
+ }
2539
+ if (!gitDir)
2540
+ return null;
2541
+ let head;
2542
+ try {
2543
+ head = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
2544
+ } catch {
2545
+ return null;
2546
+ }
2547
+ const match = /^ref:\s*(.+)$/.exec(head);
2548
+ if (!match)
2549
+ return null;
2550
+ const ref = match[1].trim();
2551
+ if (!ref.startsWith(REFS_HEADS_PREFIX))
2552
+ return null;
2553
+ return normalizeBranchName(ref);
2554
+ }
2555
+ function deriveBranchHashForCwd(cwd, saltFilePath) {
2556
+ return deriveBranchHash(readBranchName(cwd), saltFilePath);
2557
+ }
1399
2558
  }
1400
2559
  });
1401
2560
 
@@ -1404,35 +2563,69 @@ var require_hookAdapter = __commonJS({
1404
2563
  "../packages/tool-kit/out/hookAdapter.js"(exports) {
1405
2564
  "use strict";
1406
2565
  Object.defineProperty(exports, "__esModule", { value: true });
1407
- exports.DEFAULT_API_BASE_URL = void 0;
2566
+ exports.MissingInstallationIdError = exports.DEFAULT_API_BASE_URL = void 0;
2567
+ exports.resolveCliAgentInstallationId = resolveCliAgentInstallationId;
1408
2568
  exports.resolveContextHashes = resolveContextHashes;
1409
2569
  exports.loadCliAgentConfig = loadCliAgentConfig;
1410
2570
  exports.deliverHookEvents = deliverHookEvents;
1411
2571
  var contextRegistry_1 = require_contextRegistry();
2572
+ var credentials_1 = require_credentials();
2573
+ var forgeProject_1 = require_forgeProject();
1412
2574
  var eventLog_1 = require_eventLog();
1413
2575
  var eventSender_1 = require_eventSender();
2576
+ var stateStore_1 = require_stateStore();
1414
2577
  var tokenStore_1 = require_tokenStore();
1415
2578
  var workContext_1 = require_workContext();
1416
2579
  exports.DEFAULT_API_BASE_URL = "https://api.ascenda.one";
2580
+ var MissingInstallationIdError = class extends Error {
2581
+ /** The token files that were considered — none, or too many to pick from. */
2582
+ candidates;
2583
+ toolType;
2584
+ constructor(toolType, candidates, setupCommand) {
2585
+ super(candidates.length === 0 ? `Not configured: no ASCENDA_TOOL_INSTALLATION_ID, no pairing in ~/.ascenda/credentials.json, and no ${toolType} token in ~/.ascenda/tokens/. Run: ${setupCommand}` : `Not configured: no ASCENDA_TOOL_INSTALLATION_ID, no pairing in ~/.ascenda/credentials.json, and ${candidates.length} ${toolType} tokens in ~/.ascenda/tokens/ (${candidates.join(", ")}) \u2014 refusing to guess. Export ASCENDA_TOOL_INSTALLATION_ID to choose one, or run: ${setupCommand}`);
2586
+ this.name = "MissingInstallationIdError";
2587
+ this.toolType = toolType;
2588
+ this.candidates = candidates;
2589
+ }
2590
+ };
2591
+ exports.MissingInstallationIdError = MissingInstallationIdError;
2592
+ function resolveCliAgentInstallationId(toolType, identity = {}) {
2593
+ const fromEnv = process.env.ASCENDA_TOOL_INSTALLATION_ID?.trim();
2594
+ if (fromEnv)
2595
+ return { toolInstallationId: qualify(toolType, fromEnv), source: "env" };
2596
+ const fromCredentials = identity.host ? (0, credentials_1.readHostCredentials)(identity.host)?.toolInstallationId?.trim() : void 0;
2597
+ if (fromCredentials)
2598
+ return { toolInstallationId: qualify(toolType, fromCredentials), source: "credentials" };
2599
+ const candidates = (0, tokenStore_1.listPersistedToolInstallationIds)(toolType);
2600
+ if (candidates.length === 1)
2601
+ return { toolInstallationId: candidates[0], source: "disk" };
2602
+ throw new MissingInstallationIdError(toolType, candidates, identity.setupCommand ?? defaultSetupCommand(identity.host));
2603
+ }
2604
+ function defaultSetupCommand(host) {
2605
+ return host ? `npx @ascenda-one/${host.replace(/_cli$/, "")}-hooks setup` : "the agent's setup command";
2606
+ }
2607
+ function qualify(toolType, value) {
2608
+ return value.includes(":") ? value : `${toolType}:${value}`;
2609
+ }
1417
2610
  function resolveContextHashes(cwd) {
1418
2611
  const workspaceOverride = process.env.ASCENDA_WORKSPACE_HASH?.trim() || null;
1419
2612
  const projectOverride = process.env.ASCENDA_PROJECT_HASH?.trim() || null;
1420
2613
  if (workspaceOverride && projectOverride)
1421
2614
  return { workspaceHash: workspaceOverride, projectHash: projectOverride };
1422
2615
  const context = (0, workContext_1.deriveWorkContext)(cwd ?? process.cwd());
1423
- if (context)
2616
+ if (context) {
1424
2617
  (0, contextRegistry_1.recordWorkContext)(context);
2618
+ (0, forgeProject_1.recordForgeProjectAlias)(context);
2619
+ }
1425
2620
  return {
1426
2621
  workspaceHash: workspaceOverride ?? context?.workspaceHash ?? null,
1427
2622
  projectHash: projectOverride ?? context?.projectHash ?? null
1428
2623
  };
1429
2624
  }
1430
- function loadCliAgentConfig(toolType, sessionIdFromHook, cwd) {
1431
- const apiBaseUrl = (process.env.ASCENDA_API_BASE_URL ?? exports.DEFAULT_API_BASE_URL).replace(/\/$/, "");
1432
- const toolInstallationIdRaw = process.env.ASCENDA_TOOL_INSTALLATION_ID;
1433
- if (!toolInstallationIdRaw)
1434
- throw new Error("Missing ASCENDA_TOOL_INSTALLATION_ID");
1435
- const toolInstallationId = toolInstallationIdRaw.trim().includes(":") ? toolInstallationIdRaw.trim() : `${toolType}:${toolInstallationIdRaw.trim()}`;
2625
+ function loadCliAgentConfig(toolType, sessionIdFromHook, cwd, identity = {}) {
2626
+ const credentials = identity.host ? (0, credentials_1.readHostCredentials)(identity.host) : void 0;
2627
+ const apiBaseUrl = (process.env.ASCENDA_API_BASE_URL ?? credentials?.apiBaseUrl ?? exports.DEFAULT_API_BASE_URL).replace(/\/$/, "");
2628
+ const { toolInstallationId } = resolveCliAgentInstallationId(toolType, identity);
1436
2629
  const tokenFilePath = process.env.ASCENDA_EVENT_WRITE_TOKEN_FILE ?? (0, tokenStore_1.defaultTokenFilePath)(toolInstallationId);
1437
2630
  const fileToken = (0, tokenStore_1.readTokenFile)(tokenFilePath);
1438
2631
  const eventWriteToken = fileToken ?? process.env.ASCENDA_EVENT_WRITE_TOKEN;
@@ -1459,8 +2652,10 @@ var require_hookAdapter = __commonJS({
1459
2652
  const notice = options.onNotice ?? ((message) => console.error(message));
1460
2653
  let config2;
1461
2654
  try {
1462
- config2 = loadCliAgentConfig(options.toolType, options.sessionId, options.cwd);
2655
+ config2 = loadCliAgentConfig(options.toolType, options.sessionId, options.cwd, options);
1463
2656
  } catch (error2) {
2657
+ if (error2 instanceof MissingInstallationIdError)
2658
+ journalSkippedSend(options.host, error2);
1464
2659
  const logFile = (0, eventLog_1.resolveEventLogPath)();
1465
2660
  if (!logFile)
1466
2661
  throw error2;
@@ -1500,13 +2695,19 @@ var require_hookAdapter = __commonJS({
1500
2695
  } else if (result === "auth_failed") {
1501
2696
  notice("Ascenda telemetry paused: connection revoked or expired. Re-pair via an Ascenda IDE extension or pairing-sim.");
1502
2697
  } else if (result === "transport_error") {
1503
- notice("Ascenda telemetry paused: the ingest endpoint could not be reached. Your work is unaffected.");
2698
+ notice("Ascenda telemetry paused: the ingest endpoint could not be reached; the event is kept in the outbox. Your work is unaffected.");
1504
2699
  } else {
1505
2700
  notice(`Ascenda telemetry rejected: ${result}`);
1506
2701
  }
1507
2702
  return;
1508
2703
  }
1509
2704
  }
2705
+ function journalSkippedSend(host, error2) {
2706
+ const who = host ? `${host}: ` : "";
2707
+ (0, stateStore_1.recordSendOutcome)((0, stateStore_1.unresolvedStateFilePath)(error2.toolType), (0, stateStore_1.unresolvedToolInstallationId)(error2.toolType), "skipped_no_installation_id", {
2708
+ detail: error2.candidates.length === 0 ? `${who}no ASCENDA_TOOL_INSTALLATION_ID, no credentials.json pairing, no ${error2.toolType} token file` : `${who}no ASCENDA_TOOL_INSTALLATION_ID, no credentials.json pairing, ${error2.candidates.length} ${error2.toolType} token files (${error2.candidates.join(", ")})`
2709
+ });
2710
+ }
1510
2711
  function parsePositiveInt(value) {
1511
2712
  const n = Number(value);
1512
2713
  return Number.isInteger(n) && n > 0 ? n : void 0;
@@ -1514,6 +2715,387 @@ var require_hookAdapter = __commonJS({
1514
2715
  }
1515
2716
  });
1516
2717
 
2718
+ // ../packages/tool-kit/out/cliAgentSetup.js
2719
+ var require_cliAgentSetup = __commonJS({
2720
+ "../packages/tool-kit/out/cliAgentSetup.js"(exports) {
2721
+ "use strict";
2722
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
2723
+ if (k2 === void 0) k2 = k;
2724
+ var desc = Object.getOwnPropertyDescriptor(m, k);
2725
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2726
+ desc = { enumerable: true, get: function() {
2727
+ return m[k];
2728
+ } };
2729
+ }
2730
+ Object.defineProperty(o, k2, desc);
2731
+ } : function(o, m, k, k2) {
2732
+ if (k2 === void 0) k2 = k;
2733
+ o[k2] = m[k];
2734
+ });
2735
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
2736
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
2737
+ } : function(o, v) {
2738
+ o["default"] = v;
2739
+ });
2740
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
2741
+ var ownKeys = function(o) {
2742
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
2743
+ var ar = [];
2744
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
2745
+ return ar;
2746
+ };
2747
+ return ownKeys(o);
2748
+ };
2749
+ return function(mod) {
2750
+ if (mod && mod.__esModule) return mod;
2751
+ var result = {};
2752
+ if (mod != null) {
2753
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
2754
+ }
2755
+ __setModuleDefault(result, mod);
2756
+ return result;
2757
+ };
2758
+ }();
2759
+ Object.defineProperty(exports, "__esModule", { value: true });
2760
+ exports.isCliAgentManagementCommand = isCliAgentManagementCommand;
2761
+ exports.cliAgentHookBinPath = cliAgentHookBinPath;
2762
+ exports.runCliAgentSetup = runCliAgentSetup;
2763
+ exports.writeHookSettings = writeHookSettings;
2764
+ exports.findStaleHookCommands = findStaleHookCommands;
2765
+ var crypto = __importStar(__require("crypto"));
2766
+ var fs = __importStar(__require("fs"));
2767
+ var os = __importStar(__require("os"));
2768
+ var path = __importStar(__require("path"));
2769
+ var credentials_1 = require_credentials();
2770
+ var hookAdapter_1 = require_hookAdapter();
2771
+ var http_1 = require_http();
2772
+ var tokenStore_1 = require_tokenStore();
2773
+ var MANAGEMENT_COMMANDS = /* @__PURE__ */ new Set(["setup", "install", "status", "uninstall", "-h", "--help"]);
2774
+ function isCliAgentManagementCommand(argument) {
2775
+ return argument !== void 0 && MANAGEMENT_COMMANDS.has(argument);
2776
+ }
2777
+ function cliAgentHookBinPath(binaryName) {
2778
+ return path.join((0, tokenStore_1.ascendaHome)(), "bin", binaryName);
2779
+ }
2780
+ function usage(spec) {
2781
+ return `${spec.binaryName} setup \u2014 wire ${spec.displayName} to Ascenda telemetry
2782
+
2783
+ npx ${spec.packageName} setup [options]
2784
+ npx ${spec.packageName} status
2785
+ npx ${spec.packageName} uninstall
2786
+
2787
+ Options
2788
+ --api-base-url <url> ingest host (default ${hookAdapter_1.DEFAULT_API_BASE_URL})
2789
+ --local [port] shorthand for the local dev server (default port 4477)
2790
+ --tool-installation-id <id> reuse an existing pairing instead of creating one
2791
+ --token <eventWriteToken> reuse an existing token (stored 0600, never printed)
2792
+ --scope project|user where hooks are registered (default project)
2793
+ --project-dir <path> project root for --scope project (default cwd)
2794
+ --dry-run print what would change, write nothing
2795
+ -h, --help
2796
+ `;
2797
+ }
2798
+ async function runCliAgentSetup(argv, spec) {
2799
+ let options;
2800
+ try {
2801
+ options = parseArgs(argv, spec);
2802
+ } catch (error2) {
2803
+ console.error(error2 instanceof Error ? error2.message : String(error2));
2804
+ return 1;
2805
+ }
2806
+ if (options.action === "help") {
2807
+ console.log(usage(spec));
2808
+ return 0;
2809
+ }
2810
+ if (options.action === "status")
2811
+ return printStatus(options, spec);
2812
+ if (options.action === "uninstall")
2813
+ return uninstall(options, spec);
2814
+ const apiBaseUrl = (options.apiBaseUrl ?? (0, credentials_1.readHostCredentials)(spec.host)?.apiBaseUrl ?? hookAdapter_1.DEFAULT_API_BASE_URL).replace(/\/$/, "");
2815
+ console.log(`Ascenda setup for ${spec.displayName} \u2014 ${apiBaseUrl}`);
2816
+ const identity = await resolveIdentity(apiBaseUrl, options, spec);
2817
+ if (!identity)
2818
+ return 1;
2819
+ console.log(` pairing ${identity.toolInstallationId}${identity.paired ? " (new)" : " (existing)"}`);
2820
+ const binary = installBinary(spec, options.dryRun);
2821
+ console.log(` hook binary ${binary}`);
2822
+ if (!options.dryRun) {
2823
+ (0, credentials_1.writeHostCredentials)(spec.host, { apiBaseUrl, toolInstallationId: identity.toolInstallationId, pairedAt: (/* @__PURE__ */ new Date()).toISOString() });
2824
+ }
2825
+ console.log(` credentials ${(0, credentials_1.credentialsFilePath)()} (tools.${spec.host})`);
2826
+ const settingsFile = spec.settings.settingsPath(options.scope, options.projectDir);
2827
+ const written = writeHookSettings(settingsFile, binary, spec, options.dryRun);
2828
+ if (written === null)
2829
+ return 1;
2830
+ console.log(` hooks ${settingsFile} (${spec.hookEvents.length} events${written ? "" : ", already current"})`);
2831
+ if (options.dryRun) {
2832
+ console.log("\nDry run \u2014 nothing was written.");
2833
+ return 0;
2834
+ }
2835
+ console.log(`
2836
+ Done. ${spec.restartHint}`);
2837
+ console.log(`Check anytime: npx ${spec.packageName} status`);
2838
+ return 0;
2839
+ }
2840
+ function parseArgs(argv, spec) {
2841
+ const options = {
2842
+ scope: "project",
2843
+ projectDir: process.cwd(),
2844
+ dryRun: false,
2845
+ action: "install"
2846
+ };
2847
+ for (let i = 0; i < argv.length; i++) {
2848
+ const arg = argv[i];
2849
+ const next = () => {
2850
+ const value = argv[++i];
2851
+ if (value === void 0)
2852
+ throw new Error(`${arg} needs a value`);
2853
+ return value;
2854
+ };
2855
+ switch (arg) {
2856
+ case "setup":
2857
+ case "install":
2858
+ options.action = "install";
2859
+ break;
2860
+ case "status":
2861
+ options.action = "status";
2862
+ break;
2863
+ case "uninstall":
2864
+ options.action = "uninstall";
2865
+ break;
2866
+ case "--api-base-url":
2867
+ options.apiBaseUrl = next();
2868
+ break;
2869
+ case "--local": {
2870
+ const peek = argv[i + 1];
2871
+ const port = peek && /^\d+$/.test(peek) ? argv[++i] : "4477";
2872
+ options.apiBaseUrl = `http://localhost:${port}`;
2873
+ break;
2874
+ }
2875
+ case "--tool-installation-id":
2876
+ options.toolInstallationId = next();
2877
+ break;
2878
+ case "--token":
2879
+ options.token = next();
2880
+ break;
2881
+ case "--scope": {
2882
+ const value = next();
2883
+ if (value !== "project" && value !== "user")
2884
+ throw new Error(`--scope must be project or user, got ${value}`);
2885
+ options.scope = value;
2886
+ break;
2887
+ }
2888
+ case "--project-dir":
2889
+ options.projectDir = path.resolve(next());
2890
+ break;
2891
+ case "--dry-run":
2892
+ options.dryRun = true;
2893
+ break;
2894
+ case "-h":
2895
+ case "--help":
2896
+ options.action = "help";
2897
+ break;
2898
+ default:
2899
+ throw new Error(`unknown argument: ${arg}
2900
+
2901
+ ${usage(spec)}`);
2902
+ }
2903
+ }
2904
+ return options;
2905
+ }
2906
+ async function resolveIdentity(apiBaseUrl, options, spec) {
2907
+ const existingId = options.toolInstallationId ?? (0, credentials_1.readHostCredentials)(spec.host)?.toolInstallationId;
2908
+ if (existingId && options.token) {
2909
+ if (!options.dryRun)
2910
+ (0, tokenStore_1.persistEventWriteToken)((0, tokenStore_1.defaultTokenFilePath)(existingId), options.token);
2911
+ return { toolInstallationId: existingId, paired: false };
2912
+ }
2913
+ if (existingId && (0, tokenStore_1.readTokenFile)((0, tokenStore_1.defaultTokenFilePath)(existingId))) {
2914
+ return { toolInstallationId: existingId, paired: false };
2915
+ }
2916
+ if (options.dryRun) {
2917
+ return { toolInstallationId: existingId ?? `${spec.toolType}:<paired at run time>`, paired: false };
2918
+ }
2919
+ const toolInstallationId = existingId ?? `${spec.toolType}:${crypto.randomUUID()}`;
2920
+ let session;
2921
+ try {
2922
+ session = await (0, http_1.createPairingSession)(apiBaseUrl, toolInstallationId, spec.toolType, `${spec.displayName} on ${os.hostname()}`);
2923
+ } catch (error2) {
2924
+ console.error(`
2925
+ Could not reach ${apiBaseUrl} to pair: ${error2 instanceof Error ? error2.message : String(error2)}`);
2926
+ console.error("Start the local dev server and use --local, or pass --api-base-url for your backend.");
2927
+ return void 0;
2928
+ }
2929
+ const token = await pollForToken(apiBaseUrl, session.pairingSessionId, session.code, session.expiresAt);
2930
+ if (!token)
2931
+ return void 0;
2932
+ (0, tokenStore_1.persistEventWriteToken)((0, tokenStore_1.defaultTokenFilePath)(toolInstallationId), token);
2933
+ return { toolInstallationId, paired: true };
2934
+ }
2935
+ async function pollForToken(apiBaseUrl, pairingSessionId, code, expiresAt) {
2936
+ const deadline = Math.min(Date.parse(expiresAt) || Date.now() + 3e5, Date.now() + 3e5);
2937
+ let announced = false;
2938
+ while (Date.now() < deadline) {
2939
+ const status = await (0, http_1.getPairingStatus)(apiBaseUrl, pairingSessionId);
2940
+ if (status.status === "paired" && status.eventWriteToken)
2941
+ return status.eventWriteToken;
2942
+ if (status.status === "expired" || status.status === "cancelled") {
2943
+ console.error(`
2944
+ Pairing ${status.status}. Run setup again.`);
2945
+ return void 0;
2946
+ }
2947
+ if (!announced) {
2948
+ console.log(`
2949
+ Confirm in the Ascenda app \u2014 code ${code}`);
2950
+ console.log(" Waiting...");
2951
+ announced = true;
2952
+ }
2953
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2954
+ }
2955
+ console.error("\nPairing timed out. Run setup again.");
2956
+ return void 0;
2957
+ }
2958
+ function installBinary(spec, dryRun) {
2959
+ const target = cliAgentHookBinPath(spec.binaryName);
2960
+ if (dryRun)
2961
+ return target;
2962
+ const source = process.argv[1];
2963
+ fs.mkdirSync(path.dirname(target), { recursive: true });
2964
+ if (path.resolve(source) !== path.resolve(target)) {
2965
+ fs.copyFileSync(source, target);
2966
+ }
2967
+ if (process.platform !== "win32")
2968
+ fs.chmodSync(target, 493);
2969
+ return target;
2970
+ }
2971
+ function writeHookSettings(settingsFile, binary, spec, dryRun) {
2972
+ let settings = { ...spec.settings.scaffold ?? {} };
2973
+ const exists = fs.existsSync(settingsFile);
2974
+ if (exists) {
2975
+ const raw = fs.readFileSync(settingsFile, "utf8").trim();
2976
+ if (raw) {
2977
+ try {
2978
+ settings = JSON.parse(raw);
2979
+ } catch {
2980
+ console.error(`
2981
+ ${settingsFile} is not valid JSON. Fix or move it, then run setup again.`);
2982
+ return null;
2983
+ }
2984
+ }
2985
+ }
2986
+ const command = hookCommand(binary);
2987
+ const hooks = { ...settings.hooks ?? {} };
2988
+ for (const event of spec.hookEvents) {
2989
+ const kept = (hooks[event] ?? []).filter((entry) => !isOurs(entry, spec));
2990
+ hooks[event] = [...kept, spec.settings.entry(command, event)];
2991
+ }
2992
+ const updated = { ...settings, hooks };
2993
+ const serialised = `${JSON.stringify(updated, null, 2)}
2994
+ `;
2995
+ if (exists && fs.readFileSync(settingsFile, "utf8") === serialised)
2996
+ return false;
2997
+ if (dryRun) {
2998
+ console.log(`
2999
+ --- ${settingsFile} (dry run) ---
3000
+ ${serialised}`);
3001
+ return true;
3002
+ }
3003
+ if (exists)
3004
+ fs.copyFileSync(settingsFile, `${settingsFile}.ascenda-backup`);
3005
+ fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
3006
+ fs.writeFileSync(settingsFile, serialised, "utf8");
3007
+ return true;
3008
+ }
3009
+ function hookCommand(binary) {
3010
+ return `"${process.execPath}" "${binary}"`;
3011
+ }
3012
+ function isOurs(entry, spec) {
3013
+ const command = spec.settings.commandOf(entry);
3014
+ return typeof command === "string" && command.includes(spec.binaryName);
3015
+ }
3016
+ function findStaleHookCommands(settings, binary, spec) {
3017
+ const stale = /* @__PURE__ */ new Set();
3018
+ for (const entries of Object.values(settings.hooks ?? {})) {
3019
+ for (const entry of entries ?? []) {
3020
+ const command = spec.settings.commandOf(entry);
3021
+ if (typeof command !== "string")
3022
+ continue;
3023
+ if (!/ascenda/i.test(command) || command.includes(binary))
3024
+ continue;
3025
+ stale.add(command);
3026
+ }
3027
+ }
3028
+ return [...stale];
3029
+ }
3030
+ function readSettings(settingsFile) {
3031
+ try {
3032
+ return JSON.parse(fs.readFileSync(settingsFile, "utf8"));
3033
+ } catch {
3034
+ return {};
3035
+ }
3036
+ }
3037
+ function printStatus(options, spec) {
3038
+ const credentials = (0, credentials_1.readHostCredentials)(spec.host);
3039
+ const settingsFile = spec.settings.settingsPath(options.scope, options.projectDir);
3040
+ const binary = cliAgentHookBinPath(spec.binaryName);
3041
+ const tokenFile = credentials?.toolInstallationId ? (0, tokenStore_1.defaultTokenFilePath)(credentials.toolInstallationId) : void 0;
3042
+ const settings = readSettings(settingsFile);
3043
+ const registered = spec.hookEvents.filter((event) => (settings.hooks?.[event] ?? []).some((entry) => isOurs(entry, spec))).length;
3044
+ const stale = findStaleHookCommands(settings, binary, spec);
3045
+ console.log(`api base url ${credentials?.apiBaseUrl ?? "\u2014 not configured"}`);
3046
+ console.log(`pairing ${credentials?.toolInstallationId ?? "\u2014 not paired"}`);
3047
+ console.log(`token ${tokenFile && (0, tokenStore_1.readTokenFile)(tokenFile) ? "present" : "\u2014 missing"}`);
3048
+ console.log(`hook binary ${fs.existsSync(binary) ? binary : "\u2014 not installed"}`);
3049
+ console.log(`hooks ${registered}/${spec.hookEvents.length} registered in ${settingsFile}`);
3050
+ if (stale.length) {
3051
+ console.log(`stale hooks ${stale.length} not pointing at the installed binary \u2014 each one fails silently per event:`);
3052
+ for (const command of stale)
3053
+ console.log(` ${command}`);
3054
+ console.log(` Remove them from ${settingsFile} by hand; setup cannot tell them from a hook you wrote.`);
3055
+ }
3056
+ const healthy = credentials?.toolInstallationId && registered === spec.hookEvents.length && fs.existsSync(binary) && !stale.length;
3057
+ return healthy ? 0 : 1;
3058
+ }
3059
+ function uninstall(options, spec) {
3060
+ const settingsFile = spec.settings.settingsPath(options.scope, options.projectDir);
3061
+ if (fs.existsSync(settingsFile)) {
3062
+ try {
3063
+ const settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
3064
+ const hooks = { ...settings.hooks ?? {} };
3065
+ for (const event of Object.keys(hooks)) {
3066
+ const kept = hooks[event].filter((entry) => !isOurs(entry, spec));
3067
+ if (kept.length)
3068
+ hooks[event] = kept;
3069
+ else
3070
+ delete hooks[event];
3071
+ }
3072
+ const updated = { ...settings, hooks };
3073
+ if (!Object.keys(hooks).length)
3074
+ delete updated.hooks;
3075
+ fs.copyFileSync(settingsFile, `${settingsFile}.ascenda-backup`);
3076
+ fs.writeFileSync(settingsFile, `${JSON.stringify(updated, null, 2)}
3077
+ `, "utf8");
3078
+ console.log(`hooks removed from ${settingsFile}`);
3079
+ } catch {
3080
+ console.error(`could not parse ${settingsFile} \u2014 remove the ascenda hook entries by hand`);
3081
+ return 1;
3082
+ }
3083
+ }
3084
+ const binary = cliAgentHookBinPath(spec.binaryName);
3085
+ if (fs.existsSync(binary)) {
3086
+ fs.rmSync(binary);
3087
+ console.log(`removed ${binary}`);
3088
+ }
3089
+ if ((0, credentials_1.readHostCredentials)(spec.host)) {
3090
+ (0, credentials_1.removeHostCredentials)(spec.host);
3091
+ console.log(`removed tools.${spec.host} from ${(0, credentials_1.credentialsFilePath)()}`);
3092
+ }
3093
+ console.log(`tokens left in ${path.join((0, tokenStore_1.ascendaHome)(), "tokens")} \u2014 revoke in the Ascenda app to invalidate them`);
3094
+ return 0;
3095
+ }
3096
+ }
3097
+ });
3098
+
1517
3099
  // ../packages/tool-kit/out/turnState.js
1518
3100
  var require_turnState = __commonJS({
1519
3101
  "../packages/tool-kit/out/turnState.js"(exports) {
@@ -1727,8 +3309,9 @@ var require_out2 = __commonJS({
1727
3309
  "../packages/tool-kit/out/index.js"(exports) {
1728
3310
  "use strict";
1729
3311
  Object.defineProperty(exports, "__esModule", { value: true });
1730
- exports.workContextRegistryFilePath = exports.readWorkContextRegistry = exports.recordWorkContextAlias = exports.recordWorkContext = exports.deriveWorkContext = 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.resolveContextHashes = 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.localHourAt = exports.utcOffsetMinutesAt = exports.BUSINESS_DAY = exports.isOutsideBusinessHours = exports.isAfterHours = exports.bucketDurationMs = exports.bucketLinesChanged = exports.invitesDebrief = exports.classifyWorkMilestone = exports.isReworkGitAction = exports.classifyGitAction = exports.isVerificationCommand = exports.classifyCommand = void 0;
1731
- exports.isRetryableStatus = exports.parseIngestResponse = exports.postToolEventsBatch = exports.postToolEvent = exports.renewToolToken = exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = void 0;
3312
+ exports.consumeTurnDurationMs = exports.writeTopLevelCredentials = exports.writeMachineCredentials = exports.writeHostCredentials = exports.removeHostCredentials = exports.readMachineCredentials = exports.readHostCredentials = exports.credentialsFilePath = exports.writeHookSettings = exports.runCliAgentSetup = exports.isCliAgentManagementCommand = exports.findStaleHookCommands = exports.cliAgentHookBinPath = exports.resolveContextHashes = exports.resolveCliAgentInstallationId = exports.loadCliAgentConfig = exports.deliverHookEvents = exports.MissingInstallationIdError = exports.DEFAULT_API_BASE_URL = exports.resolveEventLogPath = exports.expandUserPath = exports.appendEventLog = exports.EVENT_LOG_ENV_VAR = exports.buildEventPayload = exports.AscendaSemanticEventError = exports.AscendaEventSender = exports.mintIdempotencyKey = exports.looksLikeCorrection = exports.outcomeForHook = exports.inferOutcome = exports.getNestedNumber = exports.getNestedString = exports.getNested = exports.getNumber = exports.getString = exports.localHourAt = exports.utcOffsetMinutesAt = exports.BUSINESS_DAY = exports.isOutsideBusinessHours = exports.isAfterHours = exports.bucketDurationMs = exports.bucketLinesChanged = exports.classifyModelClass = exports.autonomyBand = exports.invitesDebrief = exports.classifyWorkMilestone = exports.isReworkGitAction = exports.classifyGitAction = exports.isVerificationCommand = exports.classifyCommand = void 0;
3313
+ exports.postToolEvent = exports.renewToolToken = exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = exports.recordForgeProjectAlias = exports.forgeFullNameFromConfig = exports.readForgeFullName = exports.parseForgeFullName = exports.forgeProjectHash = exports.workContextRegistryFilePath = exports.readWorkContextRegistry = exports.recordWorkContextAlias = exports.recordWorkContext = exports.readBranchName = exports.normalizeBranchName = exports.deriveBranchHashForCwd = exports.deriveBranchHash = exports.deriveWorkContext = exports.hashWithMachineSalt = exports.readOrCreateMachineSalt = exports.machineSaltFilePath = exports.enforceOutboxBounds = exports.claimOutbox = exports.readOutboxSummary = exports.appendToOutbox = exports.defaultOutboxFilePath = exports.outboxDrainEnabled = exports.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE = exports.DEFAULT_OUTBOX_MAX_AGE_MS = exports.DEFAULT_OUTBOX_MAX_ENTRIES = exports.OUTBOX_DRAIN_ENV_VAR = exports.recordOutboxDiscard = exports.unresolvedToolInstallationId = exports.unresolvedStateFilePath = exports.markFailureNotified = exports.shouldAnnounceFailure = exports.recordSendOutcome = exports.readCollectorState = exports.defaultStateFilePath = exports.readTokenFile = exports.persistEventWriteToken = exports.listPersistedToolInstallationIds = exports.defaultTokenFilePath = exports.ascendaHome = exports.recordTurnStart = void 0;
3314
+ exports.isRetryableStatus = exports.parseIngestResponse = exports.postToolEventsBatch = void 0;
1732
3315
  var commandClassifier_1 = require_commandClassifier();
1733
3316
  Object.defineProperty(exports, "classifyCommand", { enumerable: true, get: function() {
1734
3317
  return commandClassifier_1.classifyCommand;
@@ -1750,6 +3333,14 @@ var require_out2 = __commonJS({
1750
3333
  Object.defineProperty(exports, "invitesDebrief", { enumerable: true, get: function() {
1751
3334
  return workMilestoneClassifier_1.invitesDebrief;
1752
3335
  } });
3336
+ var autonomyBand_1 = require_autonomyBand();
3337
+ Object.defineProperty(exports, "autonomyBand", { enumerable: true, get: function() {
3338
+ return autonomyBand_1.autonomyBand;
3339
+ } });
3340
+ var modelClassifier_1 = require_modelClassifier();
3341
+ Object.defineProperty(exports, "classifyModelClass", { enumerable: true, get: function() {
3342
+ return modelClassifier_1.classifyModelClass;
3343
+ } });
1753
3344
  var buckets_1 = require_buckets();
1754
3345
  Object.defineProperty(exports, "bucketLinesChanged", { enumerable: true, get: function() {
1755
3346
  return buckets_1.bucketLinesChanged;
@@ -1798,6 +3389,9 @@ var require_out2 = __commonJS({
1798
3389
  Object.defineProperty(exports, "looksLikeCorrection", { enumerable: true, get: function() {
1799
3390
  return payload_1.looksLikeCorrection;
1800
3391
  } });
3392
+ Object.defineProperty(exports, "mintIdempotencyKey", { enumerable: true, get: function() {
3393
+ return payload_1.mintIdempotencyKey;
3394
+ } });
1801
3395
  var eventSender_1 = require_eventSender();
1802
3396
  Object.defineProperty(exports, "AscendaEventSender", { enumerable: true, get: function() {
1803
3397
  return eventSender_1.AscendaEventSender;
@@ -1825,15 +3419,59 @@ var require_out2 = __commonJS({
1825
3419
  Object.defineProperty(exports, "DEFAULT_API_BASE_URL", { enumerable: true, get: function() {
1826
3420
  return hookAdapter_1.DEFAULT_API_BASE_URL;
1827
3421
  } });
3422
+ Object.defineProperty(exports, "MissingInstallationIdError", { enumerable: true, get: function() {
3423
+ return hookAdapter_1.MissingInstallationIdError;
3424
+ } });
1828
3425
  Object.defineProperty(exports, "deliverHookEvents", { enumerable: true, get: function() {
1829
3426
  return hookAdapter_1.deliverHookEvents;
1830
3427
  } });
1831
3428
  Object.defineProperty(exports, "loadCliAgentConfig", { enumerable: true, get: function() {
1832
3429
  return hookAdapter_1.loadCliAgentConfig;
1833
3430
  } });
3431
+ Object.defineProperty(exports, "resolveCliAgentInstallationId", { enumerable: true, get: function() {
3432
+ return hookAdapter_1.resolveCliAgentInstallationId;
3433
+ } });
1834
3434
  Object.defineProperty(exports, "resolveContextHashes", { enumerable: true, get: function() {
1835
3435
  return hookAdapter_1.resolveContextHashes;
1836
3436
  } });
3437
+ var cliAgentSetup_1 = require_cliAgentSetup();
3438
+ Object.defineProperty(exports, "cliAgentHookBinPath", { enumerable: true, get: function() {
3439
+ return cliAgentSetup_1.cliAgentHookBinPath;
3440
+ } });
3441
+ Object.defineProperty(exports, "findStaleHookCommands", { enumerable: true, get: function() {
3442
+ return cliAgentSetup_1.findStaleHookCommands;
3443
+ } });
3444
+ Object.defineProperty(exports, "isCliAgentManagementCommand", { enumerable: true, get: function() {
3445
+ return cliAgentSetup_1.isCliAgentManagementCommand;
3446
+ } });
3447
+ Object.defineProperty(exports, "runCliAgentSetup", { enumerable: true, get: function() {
3448
+ return cliAgentSetup_1.runCliAgentSetup;
3449
+ } });
3450
+ Object.defineProperty(exports, "writeHookSettings", { enumerable: true, get: function() {
3451
+ return cliAgentSetup_1.writeHookSettings;
3452
+ } });
3453
+ var credentials_1 = require_credentials();
3454
+ Object.defineProperty(exports, "credentialsFilePath", { enumerable: true, get: function() {
3455
+ return credentials_1.credentialsFilePath;
3456
+ } });
3457
+ Object.defineProperty(exports, "readHostCredentials", { enumerable: true, get: function() {
3458
+ return credentials_1.readHostCredentials;
3459
+ } });
3460
+ Object.defineProperty(exports, "readMachineCredentials", { enumerable: true, get: function() {
3461
+ return credentials_1.readMachineCredentials;
3462
+ } });
3463
+ Object.defineProperty(exports, "removeHostCredentials", { enumerable: true, get: function() {
3464
+ return credentials_1.removeHostCredentials;
3465
+ } });
3466
+ Object.defineProperty(exports, "writeHostCredentials", { enumerable: true, get: function() {
3467
+ return credentials_1.writeHostCredentials;
3468
+ } });
3469
+ Object.defineProperty(exports, "writeMachineCredentials", { enumerable: true, get: function() {
3470
+ return credentials_1.writeMachineCredentials;
3471
+ } });
3472
+ Object.defineProperty(exports, "writeTopLevelCredentials", { enumerable: true, get: function() {
3473
+ return credentials_1.writeTopLevelCredentials;
3474
+ } });
1837
3475
  var turnState_1 = require_turnState();
1838
3476
  Object.defineProperty(exports, "consumeTurnDurationMs", { enumerable: true, get: function() {
1839
3477
  return turnState_1.consumeTurnDurationMs;
@@ -1842,9 +3480,15 @@ var require_out2 = __commonJS({
1842
3480
  return turnState_1.recordTurnStart;
1843
3481
  } });
1844
3482
  var tokenStore_1 = require_tokenStore();
3483
+ Object.defineProperty(exports, "ascendaHome", { enumerable: true, get: function() {
3484
+ return tokenStore_1.ascendaHome;
3485
+ } });
1845
3486
  Object.defineProperty(exports, "defaultTokenFilePath", { enumerable: true, get: function() {
1846
3487
  return tokenStore_1.defaultTokenFilePath;
1847
3488
  } });
3489
+ Object.defineProperty(exports, "listPersistedToolInstallationIds", { enumerable: true, get: function() {
3490
+ return tokenStore_1.listPersistedToolInstallationIds;
3491
+ } });
1848
3492
  Object.defineProperty(exports, "persistEventWriteToken", { enumerable: true, get: function() {
1849
3493
  return tokenStore_1.persistEventWriteToken;
1850
3494
  } });
@@ -1867,6 +3511,46 @@ var require_out2 = __commonJS({
1867
3511
  Object.defineProperty(exports, "markFailureNotified", { enumerable: true, get: function() {
1868
3512
  return stateStore_1.markFailureNotified;
1869
3513
  } });
3514
+ Object.defineProperty(exports, "unresolvedStateFilePath", { enumerable: true, get: function() {
3515
+ return stateStore_1.unresolvedStateFilePath;
3516
+ } });
3517
+ Object.defineProperty(exports, "unresolvedToolInstallationId", { enumerable: true, get: function() {
3518
+ return stateStore_1.unresolvedToolInstallationId;
3519
+ } });
3520
+ Object.defineProperty(exports, "recordOutboxDiscard", { enumerable: true, get: function() {
3521
+ return stateStore_1.recordOutboxDiscard;
3522
+ } });
3523
+ var outbox_1 = require_outbox();
3524
+ Object.defineProperty(exports, "OUTBOX_DRAIN_ENV_VAR", { enumerable: true, get: function() {
3525
+ return outbox_1.OUTBOX_DRAIN_ENV_VAR;
3526
+ } });
3527
+ Object.defineProperty(exports, "DEFAULT_OUTBOX_MAX_ENTRIES", { enumerable: true, get: function() {
3528
+ return outbox_1.DEFAULT_OUTBOX_MAX_ENTRIES;
3529
+ } });
3530
+ Object.defineProperty(exports, "DEFAULT_OUTBOX_MAX_AGE_MS", { enumerable: true, get: function() {
3531
+ return outbox_1.DEFAULT_OUTBOX_MAX_AGE_MS;
3532
+ } });
3533
+ Object.defineProperty(exports, "DEFAULT_OUTBOX_DRAIN_BATCH_SIZE", { enumerable: true, get: function() {
3534
+ return outbox_1.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE;
3535
+ } });
3536
+ Object.defineProperty(exports, "outboxDrainEnabled", { enumerable: true, get: function() {
3537
+ return outbox_1.outboxDrainEnabled;
3538
+ } });
3539
+ Object.defineProperty(exports, "defaultOutboxFilePath", { enumerable: true, get: function() {
3540
+ return outbox_1.defaultOutboxFilePath;
3541
+ } });
3542
+ Object.defineProperty(exports, "appendToOutbox", { enumerable: true, get: function() {
3543
+ return outbox_1.appendToOutbox;
3544
+ } });
3545
+ Object.defineProperty(exports, "readOutboxSummary", { enumerable: true, get: function() {
3546
+ return outbox_1.readOutboxSummary;
3547
+ } });
3548
+ Object.defineProperty(exports, "claimOutbox", { enumerable: true, get: function() {
3549
+ return outbox_1.claimOutbox;
3550
+ } });
3551
+ Object.defineProperty(exports, "enforceOutboxBounds", { enumerable: true, get: function() {
3552
+ return outbox_1.enforceOutboxBounds;
3553
+ } });
1870
3554
  var salt_1 = require_salt();
1871
3555
  Object.defineProperty(exports, "machineSaltFilePath", { enumerable: true, get: function() {
1872
3556
  return salt_1.machineSaltFilePath;
@@ -1881,6 +3565,18 @@ var require_out2 = __commonJS({
1881
3565
  Object.defineProperty(exports, "deriveWorkContext", { enumerable: true, get: function() {
1882
3566
  return workContext_1.deriveWorkContext;
1883
3567
  } });
3568
+ Object.defineProperty(exports, "deriveBranchHash", { enumerable: true, get: function() {
3569
+ return workContext_1.deriveBranchHash;
3570
+ } });
3571
+ Object.defineProperty(exports, "deriveBranchHashForCwd", { enumerable: true, get: function() {
3572
+ return workContext_1.deriveBranchHashForCwd;
3573
+ } });
3574
+ Object.defineProperty(exports, "normalizeBranchName", { enumerable: true, get: function() {
3575
+ return workContext_1.normalizeBranchName;
3576
+ } });
3577
+ Object.defineProperty(exports, "readBranchName", { enumerable: true, get: function() {
3578
+ return workContext_1.readBranchName;
3579
+ } });
1884
3580
  var contextRegistry_1 = require_contextRegistry();
1885
3581
  Object.defineProperty(exports, "recordWorkContext", { enumerable: true, get: function() {
1886
3582
  return contextRegistry_1.recordWorkContext;
@@ -1894,6 +3590,22 @@ var require_out2 = __commonJS({
1894
3590
  Object.defineProperty(exports, "workContextRegistryFilePath", { enumerable: true, get: function() {
1895
3591
  return contextRegistry_1.workContextRegistryFilePath;
1896
3592
  } });
3593
+ var forgeProject_1 = require_forgeProject();
3594
+ Object.defineProperty(exports, "forgeProjectHash", { enumerable: true, get: function() {
3595
+ return forgeProject_1.forgeProjectHash;
3596
+ } });
3597
+ Object.defineProperty(exports, "parseForgeFullName", { enumerable: true, get: function() {
3598
+ return forgeProject_1.parseForgeFullName;
3599
+ } });
3600
+ Object.defineProperty(exports, "readForgeFullName", { enumerable: true, get: function() {
3601
+ return forgeProject_1.readForgeFullName;
3602
+ } });
3603
+ Object.defineProperty(exports, "forgeFullNameFromConfig", { enumerable: true, get: function() {
3604
+ return forgeProject_1.forgeFullNameFromConfig;
3605
+ } });
3606
+ Object.defineProperty(exports, "recordForgeProjectAlias", { enumerable: true, get: function() {
3607
+ return forgeProject_1.recordForgeProjectAlias;
3608
+ } });
1897
3609
  var liveBus_1 = require_liveBus();
1898
3610
  Object.defineProperty(exports, "emitLiveSignal", { enumerable: true, get: function() {
1899
3611
  return liveBus_1.emitLiveSignal;