@cachly-dev/mcp-server 0.10.118 → 0.10.119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/src/editor.d.ts +9 -0
- package/dist/src/editor.js +63 -0
- package/dist/src/editor.js.map +1 -0
- package/dist/src/funnel-milestones.d.ts +5 -0
- package/dist/src/funnel-milestones.js +48 -0
- package/dist/src/funnel-milestones.js.map +1 -0
- package/dist/src/handlers/brain.js +8 -1
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +82 -14
- package/dist/src/index.js.map +1 -1
- package/glama.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
interface DeviceFlowState {
|
|
3
|
+
deviceCode: string;
|
|
4
|
+
userCode: string;
|
|
5
|
+
verifyUrl: string;
|
|
6
|
+
pollInterval: number;
|
|
7
|
+
deadline: number;
|
|
8
|
+
polling: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Once the device code is issued, poll in the BACKGROUND on the flow's interval
|
|
12
|
+
* so browser sign-in self-completes. Without this, the flow only advanced when
|
|
13
|
+
* the user manually triggered ANOTHER tool call — the #1 onboarding drop-off
|
|
14
|
+
* (report: device flow 36→1 = 3%). Now the user signs in and their Brain
|
|
15
|
+
* activates on its own; their next natural request just works instead of
|
|
16
|
+
* showing "still waiting". Bounded by the flow deadline, timer unref'd so it
|
|
17
|
+
* never keeps the process alive, fully fail-safe (never throws). `poll` and
|
|
18
|
+
* `isCurrent` are injectable for testing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function startBackgroundDevicePoll(flow: DeviceFlowState, poll?: (f: DeviceFlowState) => Promise<'pending' | 'expired' | 'done'>, isCurrent?: (f: DeviceFlowState) => boolean, schedule?: (fn: () => void, ms: number) => void): void;
|
|
2
21
|
import { tokenize, splitMultiQuery, levenshtein, recencyBoost, extractTimestamp, STOPWORDS, katakanaToRomaji, arabicLightStem, expandCrossLingual, CROSS_LINGUAL_MAP } from './search.js';
|
|
3
22
|
export { tokenize, splitMultiQuery, levenshtein, recencyBoost, extractTimestamp, STOPWORDS, katakanaToRomaji, arabicLightStem, expandCrossLingual, CROSS_LINGUAL_MAP };
|
|
4
23
|
export declare const EDITOR_FILES: Record<string, string>;
|
package/dist/src/index.js
CHANGED
|
@@ -524,6 +524,60 @@ async function pollDeviceFlow(flow) {
|
|
|
524
524
|
return 'pending';
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Mutex wrapper so the background poller (below) and a user-triggered tool call
|
|
529
|
+
* never poll the same device code concurrently — a double poll can race the
|
|
530
|
+
* one-time JWT→API-key exchange.
|
|
531
|
+
*/
|
|
532
|
+
async function pollDeviceFlowGuarded(flow) {
|
|
533
|
+
if (flow.polling)
|
|
534
|
+
return 'pending'; // another poll owns this tick
|
|
535
|
+
flow.polling = true;
|
|
536
|
+
try {
|
|
537
|
+
return await pollDeviceFlow(flow);
|
|
538
|
+
}
|
|
539
|
+
finally {
|
|
540
|
+
flow.polling = false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Once the device code is issued, poll in the BACKGROUND on the flow's interval
|
|
545
|
+
* so browser sign-in self-completes. Without this, the flow only advanced when
|
|
546
|
+
* the user manually triggered ANOTHER tool call — the #1 onboarding drop-off
|
|
547
|
+
* (report: device flow 36→1 = 3%). Now the user signs in and their Brain
|
|
548
|
+
* activates on its own; their next natural request just works instead of
|
|
549
|
+
* showing "still waiting". Bounded by the flow deadline, timer unref'd so it
|
|
550
|
+
* never keeps the process alive, fully fail-safe (never throws). `poll` and
|
|
551
|
+
* `isCurrent` are injectable for testing.
|
|
552
|
+
*/
|
|
553
|
+
export function startBackgroundDevicePoll(flow, poll = pollDeviceFlowGuarded, isCurrent = (f) => _deviceFlow === f, schedule = defaultUnrefTimeout) {
|
|
554
|
+
const tick = async () => {
|
|
555
|
+
if (!isCurrent(flow))
|
|
556
|
+
return; // superseded or already completed → stop
|
|
557
|
+
let status = 'pending';
|
|
558
|
+
try {
|
|
559
|
+
status = await poll(flow);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
/* keep polling — a transient network error must not abort sign-in */
|
|
563
|
+
}
|
|
564
|
+
if (status === 'done')
|
|
565
|
+
return; // poll already set JWT + persisted the key
|
|
566
|
+
if (status === 'expired') {
|
|
567
|
+
if (isCurrent(flow))
|
|
568
|
+
_deviceFlow = null;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (isCurrent(flow))
|
|
572
|
+
schedule(tick, flow.pollInterval);
|
|
573
|
+
};
|
|
574
|
+
schedule(tick, flow.pollInterval);
|
|
575
|
+
}
|
|
576
|
+
function defaultUnrefTimeout(fn, ms) {
|
|
577
|
+
const t = setTimeout(fn, ms);
|
|
578
|
+
if (typeof t.unref === 'function')
|
|
579
|
+
t.unref();
|
|
580
|
+
}
|
|
527
581
|
// ── Embeddings (imported from embeddings.ts) ────────────────────────────────
|
|
528
582
|
import { setEmbedJwt } from './embeddings.js';
|
|
529
583
|
// ── Search Engine (imported from search.ts) ─────────────────────────────────
|
|
@@ -697,6 +751,8 @@ import { installClsPostCommitHook, CLS_HOOK_VERSION } from './cls-hook.js';
|
|
|
697
751
|
import { installAmbientHooks, AMBIENT_HOOK_VERSION } from './ambient-hooks.js';
|
|
698
752
|
import { runAmbient, truncateToTokens, parseHookPayload, stopObservation } from './ambient-cli.js';
|
|
699
753
|
import { appendLedgerEntry, readLedger, defaultLedgerPath } from './ambient-ledger.js';
|
|
754
|
+
import { detectEditor as detectEditorImpl } from './editor.js';
|
|
755
|
+
import { milestoneSent, markMilestoneSent } from './funnel-milestones.js';
|
|
700
756
|
import { netBalance, shouldBackoff } from './ambient-recall.js';
|
|
701
757
|
import { handleShareTool } from './handlers/share.js';
|
|
702
758
|
import { handleVizTool } from './handlers/viz.js';
|
|
@@ -707,12 +763,10 @@ import { TOOLS } from './tools.js';
|
|
|
707
763
|
let _telemetryPingSent = false;
|
|
708
764
|
// Fires once per process on the first successful Brain tool call — key activation metric
|
|
709
765
|
let _firstCallSuccessSent = false;
|
|
766
|
+
// Host detection lives in editor.ts (pure + unit-tested). Kept as a thin
|
|
767
|
+
// wrapper so the many call sites here stay unchanged.
|
|
710
768
|
function detectEditor() {
|
|
711
|
-
return
|
|
712
|
-
: process.env.WINDSURF_SESSION_ID ? 'windsurf'
|
|
713
|
-
: process.env.GITHUB_COPILOT_WORKSPACE ? 'copilot'
|
|
714
|
-
: process.env.CLAUDE_CODE_ENTRYPOINT ? 'claude'
|
|
715
|
-
: 'unknown';
|
|
769
|
+
return detectEditorImpl();
|
|
716
770
|
}
|
|
717
771
|
/** Derive an anonymous, non-reversible fingerprint from the JWT sub claim. */
|
|
718
772
|
function _jwtUserFingerprint(jwt) {
|
|
@@ -852,7 +906,7 @@ async function handleTool(name, args) {
|
|
|
852
906
|
// 1st call: start device flow, return code + URL
|
|
853
907
|
// 2nd+ calls: poll for token; once authenticated, proceed transparently
|
|
854
908
|
if (_deviceFlow) {
|
|
855
|
-
const result = await
|
|
909
|
+
const result = await pollDeviceFlowGuarded(_deviceFlow);
|
|
856
910
|
if (result === 'done') {
|
|
857
911
|
// Auth complete — re-enter handleTool with now-valid JWT
|
|
858
912
|
return handleTool(name, args);
|
|
@@ -862,14 +916,14 @@ async function handleTool(name, args) {
|
|
|
862
916
|
_deviceFlow = null;
|
|
863
917
|
return '⌛ **Authentication timed out.** Please call any tool again to restart the sign-in flow.';
|
|
864
918
|
}
|
|
865
|
-
// Still pending
|
|
919
|
+
// Still pending — the background poller will complete sign-in on its own.
|
|
866
920
|
return [
|
|
867
|
-
'⏳ **
|
|
921
|
+
'⏳ **Signing you in…**',
|
|
868
922
|
'',
|
|
869
|
-
`
|
|
870
|
-
`
|
|
923
|
+
`Finish in your browser: **${_deviceFlow.verifyUrl}**`,
|
|
924
|
+
`Code: **${_deviceFlow.userCode}**`,
|
|
871
925
|
'',
|
|
872
|
-
'
|
|
926
|
+
'This completes automatically once you sign in — no need to come back here.',
|
|
873
927
|
].join('\n');
|
|
874
928
|
}
|
|
875
929
|
// No pending flow — start a new one
|
|
@@ -879,6 +933,9 @@ async function handleTool(name, args) {
|
|
|
879
933
|
sendFunnelEvent('device_flow_started', { tool: name });
|
|
880
934
|
// Try to open the browser automatically — fire-and-forget, never block
|
|
881
935
|
openInBrowser(flow.verifyUrl);
|
|
936
|
+
// Poll in the background so sign-in self-completes — the user no longer
|
|
937
|
+
// has to trigger another tool call to drive the flow (the #1 drop-off).
|
|
938
|
+
startBackgroundDevicePoll(flow);
|
|
882
939
|
return [
|
|
883
940
|
'🧠 **cachly AI Brain — sign in to activate** (browser opening...)',
|
|
884
941
|
'',
|
|
@@ -886,7 +943,8 @@ async function handleTool(name, args) {
|
|
|
886
943
|
'',
|
|
887
944
|
`Code: **${flow.userCode}** (pre-filled if browser opened automatically)`,
|
|
888
945
|
'',
|
|
889
|
-
'
|
|
946
|
+
'That’s it — your Brain activates automatically the moment you finish',
|
|
947
|
+
'signing in. Just keep working; your next request arrives brain-powered.',
|
|
890
948
|
'',
|
|
891
949
|
'✨ Free forever · No credit card · 126 MCP tools · GDPR · EU servers',
|
|
892
950
|
].join('\n');
|
|
@@ -923,7 +981,12 @@ async function handleTool(name, args) {
|
|
|
923
981
|
if (brainResult !== null) {
|
|
924
982
|
if (!_firstCallSuccessSent && JWT) {
|
|
925
983
|
_firstCallSuccessSent = true;
|
|
926
|
-
|
|
984
|
+
// Persistent guard: ambient hooks spawn a fresh process per prompt, so the
|
|
985
|
+
// in-memory flag alone re-sent "first call" on every hook invocation.
|
|
986
|
+
if (!milestoneSent('first_call_success')) {
|
|
987
|
+
markMilestoneSent('first_call_success');
|
|
988
|
+
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
|
|
989
|
+
}
|
|
927
990
|
}
|
|
928
991
|
// Per-tool telemetry — fires after every successful brain tool call.
|
|
929
992
|
// api_key carries the cky_live_... token so the backend can resolve tenant + increment counters.
|
|
@@ -1026,7 +1089,12 @@ async function handleTool(name, args) {
|
|
|
1026
1089
|
if (contextResult !== null) {
|
|
1027
1090
|
if (!_firstCallSuccessSent && JWT) {
|
|
1028
1091
|
_firstCallSuccessSent = true;
|
|
1029
|
-
|
|
1092
|
+
// Persistent guard: ambient hooks spawn a fresh process per prompt, so the
|
|
1093
|
+
// in-memory flag alone re-sent "first call" on every hook invocation.
|
|
1094
|
+
if (!milestoneSent('first_call_success')) {
|
|
1095
|
+
markMilestoneSent('first_call_success');
|
|
1096
|
+
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
|
|
1097
|
+
}
|
|
1030
1098
|
}
|
|
1031
1099
|
return contextResult;
|
|
1032
1100
|
}
|